相信有不少的同学都有使用过MySQL内置的事务属性,我觉得相对比于其他的关系性数据库来说,MySQL的事务特性确实是一个非常吸引人去使用的点。那么本文将会来带大家深入探讨一个问题,就是为什么MySQL到了5.7之后,Repeatable Read隔离级别是如何解决幻读问题的。
不同的隔离级别分别有什么作用
Read UnCommited
读取到别的事务还没有提交的内容,不能很好的保证事务的隔离性。会有脏读,不可重复读,幻读的问题。
Read Commited
只能读取到最新已经提交的事务,能够保证读取数据的最新性,但是可能会存在幻读,不可重复读的问题。
Repeatable Read
同一个事务里面,每次的读取都是相同的,但是依然可能有幻读的问题。
Serializable
序列化级别,我觉得是可以完全避免掉幻读,不可重复读,脏读三种问题的级别了,但是一旦开启,相当于就是每个事务都变成了单线程去执行,其性能是会大大降低的。
幻读,不可重复读,脏读的区别
其实这三者我觉得就是一个概念性的东西,大家大概知道下就可以了。 不可重复读:事务的前后两次读取结果都不相同,其实所谓的幻读和脏读就是从这个大类型下延伸出来的。 脏读:读取到了未提交的事务数据。 幻读:同一条sql读取到了和之前不同的结果,例如前一刻count出来是8条记录,后一刻count出来是9条记录。幻读更多强调的是数据的不一致性。
为什么说MySQL的Repeatable Read依然可能会有幻读的情况发生?
为什么说是可能,这个我们需要结合一定的业务场景来进行说明才好理解。
事务1 | 事务2 |
---|---|
begin | |
select * from t_test where id=1; | begin |
(empty resultSet) | insert into t_test(id,name) values(1,'idea'); |
commit; | |
insert into t_test(id,name) values(1,'idea'); | |
Duplicate entry '1' for key 't_test.PRIMARY'(主键冲突) | |
commit; |
例如上边我所描述的场景中,我们的事务会话1里面,对于表先做了查询的判断,发现并没有任何记录,所以进行了insert操作,但是在事务1insert之前,事务2已经抢先一步进行了insert,所以出现了主键冲突的问题,这就是一个典型的幻读问题场景。
为了避免在RR隔离级别下,会再有幻读的问题发生,我们需要怎么解决呢?
使用Next-Key Lock 算法,这种算法是只有InnoDB的RR隔离级别下才会有使用到的。我们举个场景来理解下就好懂了。
select * from t_test where user_id=10001 for update;
这里user_id是唯一的辅助索引,因此在进行查询的时候会将10001行给锁住,当有其他的事务希望对10001记录进行修改,则需要等待行锁的释放。这种情况确实可以有效避免掉幻读的发生,但是是在当前读的基础上去实现的。
如果是基于快照读的话,就不能避免幻读了,例如下边这种:
select * from t_test where user_id=10001;
所以如果以后再遇到说,RR是否可以解决幻读的问题,我们应该分场景来解答,如果是快照读的话,依旧有幻读,如果是当前读,那么就可以避免。
当前读避免幻读的原理是什么
这里我直接给结论大家看:
- 等值查询的当前读(数据存在),并且还是索引的情况
select * from t_test where user_id=10001 for update;
加入了行锁,避免外界数据对该行进行修改
- 等值查询的当前读(数据不存在),并且还是索引的情况 假设目前我们存在10001和10005,那么执行以下sql:
select * from t_test where user_id=10003 for update;
锁住的范围是:(10001,10005),所以如果有任何的更新或者插入操作希望在10002,10003,10004之间发生的话,都会被堵塞住。
- 索引范围查询当前读
select * from t_test where user_id>10003 and user_id<=10009 for update;
锁住的范围是(10003,10009],相当于是加入了一把间隙锁区避免幻读了。
- 非索引的当前读
select * from t_test where name like '%ide%' for update;
会有锁表的可能