1.四大特性
首先,事务的四大特性:ACID(原子性,一致性,隔离性,持久性)
在InnoDB引擎中,是怎么来保证这四个特性的呢?
- 持久性是通过 redo log (重做日志)来保证的;
- 原子性是通过 undo log(回滚日志) 来保证的;
- 隔离性是通过 MVCC(多版本并发控制) 或锁机制来保证的;
- 一致性则是通过持久性+原子性+隔离性来保证;
然后的话,我们这次重点讲一下事务的隔离性。
2.并发事务会带来哪些问题?
MySQL是允许多个客户端连接的,这样就会导致MySQL同时处理多个事务的情况。
那么,就会出现以下三个问题:脏读,不可重复读,幻读
脏读:A事务读到了B事务还未提交且修改过的数据。
比如A第一次读某人卡里的余额为100,然后A把这个余额修改为200但是不提交事务,此时B正好也来读取余额得到200,然后A把这个修改进行了回滚(100),那么B读到的就是脏数据了。
不可重复读:A事务前后两次读的数据不一致
比如A第一次读某人卡里余额100,然后B来把这个余额进行了修改为200并且提交了事务,那么A再一次读取卡里余额,就变成200了,前后两次读取的数据不一致。
幻读:A事务多次查询某个条件的记录数量,前后两次查询得到的记录数量不一致。
比如A第一次读取余额大于100的用户有5个,然后此时B来插入了一条数据(余额为150)并提交了事务,然后A第二次读取余额大于100的用户变成了6个,前后两次读取的记录数量不一致。
3.事务的隔离级别
3.1.四种隔离级别
那么针对上面的三个问题,SQL标准提出了四种隔离级别来规避这些现象,分别是:
- 读未提交(read uncommitted),指一个事务还没提交时,它做的变更就能被其他事务看到;
- 读提交(read committed),RC指一个事务提交之后,它做的变更才能被其他事务看到;
- 可重复读(repeatable read),RR指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,MySQL InnoDB 引擎的默认隔离级别;
- 串行化(serializable );会对记录加上读写锁,在多个事务对这条记录进行读写操作时,如果发生了读写冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行;
对于上面四种隔离级别,每个级别还存在的问题有如下:
所以,要解决脏读现象,就要升级到「读已提交」以上的隔离级别;要解决不可重复读现象,就要升级到「可重复读」的隔离级别,要解决幻读现象不建议将隔离级别升级到「串行化」。
这是因为,MySQL的InnoDB引擎在可重复读的级别下,很大程度的规避了幻读现象(不是完全规避),我们在后面重点讲可重复读这一块。
3.3.可重复读(RR)是如何工作的?
可重复读级别是在启动事务之后,第一次读取数据时,生成一个Read View,以后的本事务中每次普通查询语句都会依据这个Read View来获取数据。
3.4 读已提交(RC)是如何工作的?
读已提交级别是在启动事务后,每次读取数据时,都会生成一个新的Read View。
4.可重复读怎么很大程度解决幻读现象?
4.1针对普通select语句(快照读)
是通过MVCC的方式解决了幻读,因为RR级别下,开始事务后,在执行第一个查询语句,就会创建一个Read View,后续的查询语句,都是在这个Read View的基础上来得到数据的。所以即使中途有其他事务插入了新记录,那么也是查不出来这个新数据的。
4.2针对select..for update语句(当前读)
MySQL 里除了普通查询是快照读,其他都是当前读 ,比如 update、insert、delete,这些语句执行前都会查询最新版本的数据,然后再做进一步的操作。
那么如果在当前读的情况下没有加锁,那就会出现幻读现象,如下:
所以,InnoDB引擎为了解决 "在可重复读隔离级别下使用当前读" 而导致的幻读问题,就引出了间隙锁。
间隙锁,顾名思义是在一个范围之间加锁,那么在这个范围之内,其他事务就无法进行增删改操作了。
假设,表中有一个范围 id 为(3,5)间隙锁,那么其他事务就无法插入 id = 4 这条记录了,这样就有效的防止幻读现象的发生。
然后我们举个具体的例子:
事务A执行了这条sql语句之后,就会对表中的记录加上id范围为(2,+∞] 的next-key lock,
(这个next-key lock是间隙锁+记录锁的组合,锁的是左开右闭的区间。) 然后事务B在执行sql语句的时候,被事务A加的next-key lock给阻塞了,那么事务B就会生成一个意向锁,等待事务A提交之后,再进行插入操作,这样就避免了因为B事务插入新数据而导致A事务出现幻读的情况。
5.可重复读级别下幻读被完全解决了吗?
通过上面两种情况所做的处理,可以说是很大程度上避免了幻读现象,但是还没有完全解决幻读现象。
例如:
5.1.可重复读级别下幻读情况1
- A开启事务,并且查询id=5的数据,不存在。
- B开启事务,插入一条id=5的数据,并提交事务。
- A直接进行update操作,修改id=5的数据,此时如果再次执行查询id=5的操作,那么就会查询到id=5的数据了!
这种情况就是在A开启事务后,通过普通的sql语句生成了一个Read View,之后事务B向表中插入新数据并提交。紧接着事务A对id=5的数据进行update操作,我们前面提到要进行update操作,就必须获取到**当前读,**那么这样的话,就会导致事务A再次查询的时候,查询的就是最新的数据,就会出现幻读了。
5.2.可重复读级别下幻读情况2
- A开启事务,然后执行:select * from t_test where id > 100 (快照读)得到3条记录。
- B开启事务,往里面插入了一个 id=200 的记录并提交事务。
- A继续执行:select * from t_test where id > 100 for update (当前读)得到4条记录。
这种情况就属于,A开启事务后没有马上执行select...for update操作,导致第一次读取数据时没有加next-key lock,从而导致B事务插入了新数据。
6.总结
- 事务的四大特性:ACID(原子性,一致性,隔离性,持久性)
- 持久性<-RedoLog , 一致性<-UndoLog ,隔离性<-MVCC || 锁 ,持久性<-前三个一起保证
- 并发事务带来的危害:脏读,可重复读,幻读
- 四种隔离级别:读未提交,读已提交(RC),可重复读(RR),串行化
- 可重复读解决可重复读隔离级别(默认隔离级),提出的避免幻读的方案:
- 针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读。(生成快照读,后续的读都是基于第一次的快照读来获取数据)
- 针对当前读(select ... for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读。
备注:图片摘抄自小林coding,如有侵权,联系删除。