关于幻读
不可重复读
在同一事务中,两次读取同一数据,得到内容不同,侧重点在于数据修改
幻读
同一事务中,用同样的操作读取两次,得到的记录数不相同,幻读的侧重点在于两次读取的纪录数量不一致
不可重复读和幻读在概念上有些交叉,对于不可重复读来说,在同一个事务中,如果读取到的记录数量发生变化,也可以看作是一种不可重复读,同样,对于幻读来说,同一个事务中的读取结果数量一致,但是内容发生了变化,也可以看成是一种不可重复读。
对于mysql,这里讨论一下read committed和repeatable read两个事务隔离级别的不可重复读和幻读:
read committed:
在read committed隔离级别下,存在不可重复读和幻读现象。
起两个事务1和2,1采用快照读读取数据,2修改其中一条满足1查询条件的数据并提交,这时1再快照读一次,就会发现2添加的记录,这就是不可重复读。但如果1采用当前读方式读取数据,由于读取数据的时候会给满足条件的数据加锁,因此,事务2无法修改数据内容,如果单纯从数据内容发生变化这个方面来考虑的话,是不会出现不可重复读的问题的。同时,如果考虑到记录数量增减,由于read committed隔离级别并没有gap锁,所以虽然不能修改采用当前读方式锁定的数据,但是可以在查询条件满足的范围内增加新的数据,这也可以看作是一种不可重复读,但显然这种情况划分到幻读更好。
repeatable read:
在repeatable read隔离级别下,mysql不仅解决了不可重复读,还通过gap锁的引入,解决了幻读的问题。
具体分析与上边类似,只是在repeatable隔离级别下,如果事务2对某些数据作了更新,事务1通过快照读已经不会读取到数据变化,所以repeatable read隔离级别解决了不可重复读的问题。同时由于引入了gap锁,事务2也无法在事务1采用当前读的前提下在事务1的查询条件满足范围内插入新的数据,所以记录数量不会发生变化,也就不存在幻读问题。
当隔离级别是可重复读,且禁用innodb_locks_unsafe_for_binlog的情况下,才可以利用gap锁避免幻读,gap锁是为了保证语句级别binlog的串行化。
总结:
- 如果仅仅考虑数据内容发生变化来衡量不可重复读,那么只有在read committed隔离级别的快照读中才会出现不可重复读,如果考虑数据数量变化,那么在read committed隔离级别的快照读和当前读中都存在不可重复读现象;
- read committed隔离级别下,快照读和当前读都会产生幻读现象;
- repeatable read隔离级别下,只有快照读会产生幻读现象,当前读已经通过gap锁的引入消除了幻读现象。
(何登成大神在博客中将幻读的定义限制在当前读的前提下,个人认为还是幻读和可重复读的具体定义不清晰)