事务的特性
ACID(Atomicity、Consistency、Isolation、Durability,即原子性、一致性、隔离性、持久性)
- Atomicity:原子性,执行的最小单元,不可拆分。
- Consistency:一致性,事务在执行DML操作时,要么成功,要么失败。
- Isolation:隔离性。两个事务执行具有隔离性。
- Durability:持久性。事务执行以后,对数据库的影响是持久的。
mysql事务常用的命令
-
开启事务:两个都可以
begin; start transaction;
-
提交事务
commit;
-
回滚事务
rollback;
-
查看当前对话的隔离级别
select @@tx_isolation;
-
查看系统当前的隔离级别
select @@global.tx_isolation;
-
设置会话级隔离级别为读未提交:
set session transaction isolation level read uncommitted;
-
设置全局级隔离级别为读未提交:
set global transaction isolation level read uncommitted;
-
设置自动提交
set autocommit = 0/1; (0为不自动提交,1为自动提交)
事务的隔离级别
作用范围
- 全局级:对所有的会话有效
- 会话级:只对当前的会话有效
事务隔离级别
从上往下,并发性越来越差,安全性越来越高。
-
读未提交
Read UnCommitted。一个事务可以看到另一个事务未提交的数据。一般很少用。
带来的问题:脏读。
-
读已提交
Read Commited,简称RC。大多数数据库系统默认RC。
定义:一个事务开始时,只能"看见"已经提交的事务做的修改,也就是说:一个事务从开始到提交之前,所做的任何修改对其他事务都是不可见的。
带来的问题:不可重复读,即两次执行相同的查询,可能会得到不同的结果。
-
可重复读
Repeatable Read,简称RR。
同一个事务多次读取同样的记录的结果是一致的。
带来的问题:幻读,即当某个事务在读取某个范围内的记录时,另一个事务又在该范围插入了新的记录,当某个事务再次读取该范围内的记录时,会产生幻行。
InnoDB和XtraDB通过MVCC解决了幻读的问题。
-
串行化
Serializable。读写都会加锁,锁住整张表,很少用。
通过强制事务串行执行,避免幻读问题。Serializable会在每一行数据上都加锁,所以可能会导致大量超时和锁争用的问题。
事务隔离级别并发问题
特别说明点
- 默认隔离层级:mysql—可重复读。oracle,sql server —读已提交
- 事务隔离级别为可重复读时,如果检索条件有索引(包括主键索引)的时候,默认加锁方式是next-key 锁;如果检索条件没有索引,更新数据时会锁住整张表。一个间隙被事务加了锁,其他事务是不能在这个间隙插入记录的,这样可以防止幻读。
如何设置事务级别
使用mysql命令对数据库进行操作。
mysql默认隔离层级
查询mysql的默认事务隔离级别的命令:REPEATABLE-READ
事务隔离层级
以读未提交为例,其他的内容参考最下面推荐的文章。
执行的内容:在事务A开启读未提交时,事务B更新数据时,事务A就能查到数据。
事务B做操作:开启读未提交命令,还未提交事务,事务A就能查到数据。一旦客户端B的事务因为某种原因回滚,所有的操作都将会被撤销,那客户端A查询到的数据其实就是脏数据。
如何实现事务隔离
- 事务隔离的实现:每条记录在更新的时候都会同时记录一条回滚操作。同一条记录在系统中可以存在多个版本,这就是数据库的多版本并发控制(MVCC)。
- 回滚日志什么时候删除?系统会判断当没有事务需要用到这些回滚日志的时候,回滚日志会被删除。
- 什么时候不需要了?当系统里没有比这个回滚日志更早的read-view的时候。
长事务
什么是长事务?其实就是运行时间比较长的未提交的事务。
长事务的影响
- 长事务意味着系统里面会存在很老的事务视图,在这个事务提交之前,回滚记录都要保留,这会导致大量占用存储空间。
- 长事务还会占用锁资源,可能会拖垮数据库。
如何查询出表中的长事务
select * from information_schema.innodb_trx;
这个表中记录了所有正在运行的事务信息,里面有事务的开始时间。可以从这里看出哪些事务运行的时间比较长。
如何避免长事务
- 首先,从应用开发端来看:
- 确认是否使用了 set autocommit=0。这个确认工作可以在测试环境中开展,把 MySQL 的 general_log 开起来,然后随便跑一个业务逻辑,通过 general_log 的日志来确认。一般框架如果会设置这个值,也就会提供参数来控制行为,你的目标就是把它改成 1。
- 确认是否有不必要的只读事务。有些框架会习惯不管什么语句先用 begin/commit 框起来。有些是业务并没有这个需要,但是也把好几个 select 语句放到了事务中。这种只读事务可以去掉。
- 业务连接数据库的时候,根据业务本身的预估,通过 SET MAX_EXECUTION_TIME 命令,来控制每个语句执行的最长时间,避免单个语句意外执行太长时间。(为什么会意外?在后续的文章中会提到这类案例)
- 其次,从数据库端来看:
- 监控 information_schema.Innodb_trx 表,设置长事务阈值,超过就报警 / 或者 kill;
- Percona 的 pt-kill 这个工具不错,推荐使用;
- 在业务功能测试阶段要求输出所有的 general_log,分析日志行为提前发现问题;
- 如果使用的是 MySQL 5.6 或者更新版本,把 innodb_undo_tablespaces 设置成 2(或更大的值)。如果真的出现大事务导致回滚段过大,这样设置后清理起来更方便。
事务启动的方式
- 显式启动事务语句,begin或者start transaction,提交commit,回滚rollback;
- set autocommit=0,该命令会把这个线程的自动提交关掉。这样只要执行一个select语句,事务就启动,并不会自动提交,直到主动执行commit或rollback或断开连接。
事务与Spring结合
事务如何传播:即方法A传播到方法B。那方法A到方法B,方法A和B中分别要么有事务,要么没有事务。那在传播时有如下的行为:
事务传播行为(Transaction Propagation Behavior)
目前,Spring在TransactionDefinition类中定义了以下7种传播特性,具体特性我们接下来会分析:
- PROPAGATION_REQUIRED:如果不存在外层事务,就主动创建事务;否则使用外层事务;
- PROPAGATION_SUPPORTS:如果不存在外层事务,就不开启事务;否则使用外层事务;
- PROPAGATION_MANDATORY:如果不存在外层事务,就抛出异常;否则使用外层事务;
- PROPAGATION_REQUIRES_NEW:总是主动开启事务;如果存在外层事务,就将外层事务挂起;
- PROPAGATION_NOT_SUPPORTED:总是不开启事务;如果存在外层事务,就将外层事务挂起;
- PROPAGATION_NEVER:总是不开启事务;如果存在外层事务,则抛出异常;
- PROPAGATION_NESTED:如果不存在外层事务,就主动创建事务;否则创建嵌套的子事务;
死锁问题
死锁是什么
死锁是指两个或者多个事务互相争夺同一个资源导致的。两个或者多个事务在同一个资源上相互占用,并请求锁定对方占用的资源,从而产生恶性循环的现象。当多个事务试图以不同的顺序锁定资源时,就可能会产生死锁。
如何避免死锁
数据库系统实现了各种死锁检测和死锁超时检测。InnoDB可以检测到死锁的循环依赖,并立即返回一个错误。InnoDB目前处理死锁的方法是,将持有最少行级排它锁的事务进行回滚(这是相对比较简单的死锁回滚算法)。
锁的行为和顺序是和存储引擎有关的。不同存储引擎在执行语句时,有些会产生死锁,有些则不会。
只有部分或者全部的事务回滚时,才能打破死锁。所以在程序设计时需要考虑如何处理死锁。
推荐文章
Mysql常用命令行大全
MySQL的四种事务隔离级别
Transaction 那点事儿