幻读
author:陈镇坤27
创建时间:2021年11月24日14:29:44
编辑时间:2021年11月25日、2021年12月16日、2022年4月1日
转载请注明出处
文章目录
- 幻读
- 问:什么是幻读?
- 问:如果一个事务中的多条相同SQL查询加排他锁,使用的是全表扫描,但只对符合条件的数据加行锁,那会怎么样?
- 问:幻读与脏读的区别?
- 问:在可重复读的隔离级别下,幻读只会在查询为哪种性质时才会出现?
- 问:幻读与事务的可见性规则冲突吗?
- 问:如果在事务中查询数据(加排他锁),不同次数的执行,只存在行锁,会有什么问题?
- 问:MySQL是如何解决可重复读下的更新(是更新的当前读!而非查询的)可能引发的幻读(说是幻更更好?)问题的?详细介绍下。
- 问:什么是next-key lock?解释一下。
- 问:引入间隙锁后,可能导致什么问题?
- 问:除了引入间隙锁, 还有什么方式可以解决幻读?
- 问:为什么RC下,需要将binlog改为row格式呢?
- 问:如果库是RC的,那逻辑备份的时候,为什么需要改为RR呢?
- 问:如果备份从库时候,从库是RR,主库是RC,主库正常执行业务,那会有问题吗?
- 问:一条加了排他锁的查询语句,如果查询是全表扫描,那么扫描过的语句会如何?
- 问:加了写锁行数据,为什么可以被select?不是说读写互斥吗?
- 问:如果是唯一索引进行当前读操作,where精确命中条件,则这种场景会出现幻读吗?
- 扩展内容:
————————————————————————————————
问:什么是幻读?
答:可重复读隔离级别下,事务内查询用“当前读”,读到本事务外新增的数据。
幻读是用户在使用可重读读隔离级别下,在进行select查询时加X、S锁才可能出现的,是用户主动打破业务层面上的查询时的一致性视图隔离性。其不属于事务隔离的可见性规则问题(可见性规则是来解决各种事务问题的),而是用户在使用上出现的问题,属于业务问题。
问:如果一个事务中的多条相同SQL查询加排他锁,使用的是全表扫描,但只对符合条件的数据加行锁,那会怎么样?
答:在多个会话操作下,即使隔离级别RR,但由于排他锁查询是当前读,所以SQL查询的结果可能不一致,例如(下图)其他事务在其查询期间往表中更新、新增数据。其中幻读仅由insert数据导致,并且在可重复隔离级别下,仅当前读会产生。
问:幻读与脏读的区别?
答:幻读是读到了提交了的数据,而脏读是读到了没提交的脏数据。在读提交的隔离别级别下,没有讨论幻读的实在意义。而在可重复读隔离级别下,当前读打破了视图的隔离限制,实现了读到不应该读的数据的作用。
问:在可重复读的隔离级别下,幻读只会在查询为哪种性质时才会出现?
答:由于一致性视图作用,因此幻读只会在“当前读”情况下发生。
问:幻读与事务的可见性规则冲突吗?
答:在可重复隔离级别下,幻读是用户选择使用当前读而产生的,符合当前读的规则,也不跟事务的可见性规则矛盾。
问:如果在事务中查询数据(加排他锁),不同次数的执行,只存在行锁,会有什么问题?
答:
语义层面:加锁的目的是语义目的是锁住我们想要的匹配条件的数据,如果只存在行锁,由于在事务开启时不能对不存在的数据添加行锁,所以可以insert数据,并且其他数据页可以被update成我们本来要匹配条件的数据,这就破坏了语义层面的加锁。
日志层面:如果只有行锁,无法限制其他数据通过update或insert造成的数据干扰问题,同时由于二阶段锁协议(见第7篇),行锁只有在锁的外层事务释放时才会进行释放,而事务提交的那一刻也才是日志真正记录的那一刻,binlog此时记录的顺序就可能被打破。
例如事务A,B,C顺序,SQL的执行顺序是ABC,但提交的顺序却是BCA,则日志记录的是BCA,在做日志恢复的时候,日志的A就可能将BC的数据进行了操作,这就导致了数据和日志的不一致性。
想解决这个问题,即使对所有的数据进行加行锁,也只能解决delete和update问题,无法解决insert问题。
问:MySQL是如何解决可重复读下的更新(是更新的当前读!而非查询的)可能引发的幻读(说是幻更更好?)问题的?详细介绍下。
答:在行锁的基础上,追加间隙锁(Gap Lock)。
间隙锁加锁的范围是索引的前后间隙。(3行数据,全表加间隙锁,则有4个间隙锁)
MySQL会对扫描经过的索引对象,加上间隙锁。
与间隙锁产生冲突的是“往锁控制的间隙中插入数据的操作”,而非其他的锁。
问:什么是next-key lock?解释一下。
答:next-key lock是间隙锁加行锁的总称。这个是一个前开后闭区间(如果是表的最后一行数据,则下一个区间间隙锁是这行数据到表索引的不存在的最大值)。
问:引入间隙锁后,可能导致什么问题?
答:首先需要了解间隙锁的特性:锁的是间隙,是禁止其他操作往间隙中插入数据。因此间隙锁与间隙锁之间不冲突。
所以在AB两个事务当中,A事务随机查询某一条数据(+排他锁)的方式产生间隙锁,B事务也执行同一条SQL,随后A事务判定数据不存在时,对其插入,B事务也同样如此操作,则最终导致A事务等待B事务的间隙锁,B事务等待A事务的间隙锁,这将导致死锁。
当然,由于有开启死锁检测,最后B事务会报错(如果客户端有重连接,那这种问题是不可感知的)。
显而易见,间隙锁会影响数据的并发度。
问:除了引入间隙锁, 还有什么方式可以解决幻读?
答:将RR改为RC,则不存在幻读问题,但此时需要将binlog格式改为row,否则可能出现数据日志不一致问题。
问:为什么RC下,需要将binlog改为row格式呢?
答:RC没有幻读问题,也没有间隙锁,但在显示控制事务更新时,若在事务A还未提交之前,有别的事务B后执行并且添加的数据是本次事务的更新语句逻辑层面会命中的条件,那等事务A提交之后,statement的binlog记录上,事务B的记录在前,而事务A的记录在后(这没毛病,谁先提交谁先记录,但恢复的时候会产生问题)。当执行日志恢复时,后提交的事务A的记录会把B的记录也进行更新,这就导致了数据恢复错误。此时,如果改成row格式,binlog会具体记录语句的各项条件,这样在恢复时便不会恢复错误了。(PS:除了恢复,还有主从库的同步也会有这个问题)
例1:
删除 statement记录的是这个删除的语句
delete from t where age>10 and modified_time<='2020-03-04' limit 1
而row格式记录的是实际受影响的数据是真实删除行的主键id
delete from t where id=3 and age=12 and modified_time='2020-03-05'
例2:
-- statemnt格式
begin;
update t set d=5 where id=0;
commit;
-- row格式
begin;
update t where id=0 and c=0 and d=0
set id=0,c=0,d=5
commit;
问:如果库是RC的,那逻辑备份的时候,为什么需要改为RR呢?
答:因为RR的一致性读视图可以保证数据备份时,不阻塞其他数据写入。
问:如果备份从库时候,从库是RR,主库是RC,主库正常执行业务,那会有问题吗?
答:不会有问题。备份是从某个快照时间之后开始的,数据是固定一致准确的。
问:一条加了排他锁的查询语句,如果查询是全表扫描,那么扫描过的语句会如何?
答:在可重复读隔离级别下,MySQL会对扫过的语句加next-key lock。详细见第21篇。
问:加了写锁行数据,为什么可以被select?不是说读写互斥吗?
答:普通的select是快照读,不需要加锁。for update不会阻塞其他事务的快照读操作。
问:如果是唯一索引进行当前读操作,where精确命中条件,则这种场景会出现幻读吗?
答:由于唯一索引作用,行数据具有唯一性,因此不会出现幻读的情况。(关于普通索引的此类场景更多内容可见第21篇)。
——————————
我的小结
可重复读更新时采用当前读,这是为了防止数据丢失,但使用当前读,可能造成更新层面语义的破坏和日志数据不一致问题,为解决更新层面的幻读问题,可重复读时,引入了next-key lock。若是直接采取读提交,则解决了语义层面的破坏,但需要通过将binlog改为max或row的形式,防止日志数据不一致。
扩展内容:
二阶段锁协议:见本专栏第7篇
binlog日志记录数据的时机:见本专栏第2篇
关于row格式的参考资料:https://cloud.tencent.com/developer/article/1595161