MySQL中幻读(Phantom Read)的正确理解

MySQL通过MVCC实现了可重复读(Repeatable Read),但并不能解决幻读,这里分析一下什么是幻读,MySQL又是如何解决的。

场景

假设记录值设置了唯一性约束。

时间点事务A事务B
1开启事务
2开启事务
3查询大于0且小于5的列表,记录数为0。(SELECT FOR UPDATE)
4插入一条值为4的记录
5提交事务
6查询大于0且小于5的列表,记录数为0。
7查询值为4的记录,记录不存在。
8插入一条值为4的记录,报错

幻行

当某个事务A范围查询数据(for update)时,另一个事务B在该范围内插入了记录a,当事务A再次范围查询时,会产生幻行(即多了事务B插入的那行数据)。

幻读

事务A继续执行,也同时插入记录4,此时由于记录4已经存在,事务会因主键冲突报错。
但前面Select时并未发现这条记录。这就是幻读。

幻读的问题

因为有唯一性约束,看上去并不会导致数据错误,但实际上已经破坏了语义。等于说Select for Update时的锁语义被破坏。
主要是因为在Select时,记录不存在,无法上锁。
假设记录值没有做唯一性约束,而是通过业务层做唯一性约束,那就可能导致唯一性约束失效。

临键锁 (Next-Key Lock)

对于幻读的问题,MySQL通过引入一个锁来解决这个问题。
就是临键锁,临键锁最终会变成以下两种是锁。

  • 索引范围内的所有记录(即记录锁)。
  • 这些记录之间的所有间隙(即间隙锁)。

临键锁通过锁定范围,避免事务执行时,范围内有插入操作。将插入操作延迟到事务后。

错误理解

由于网上也有另一种对幻读的解释,即,由于幻行的存在,影响了范围统计的结果
但实际上,这是已提交读(READ COMMITTED),可重复已经解决了这个问题。由于DB_TRX_ID的存在,新事物提交的结果并不会被查询到。范围统计的结果自然也是正确的。

其他

TiDB中也存在同样的问题,由于TiDB未支持间隙锁,因此存在幻读,需要在使用时注意此类场景。

© 版权声明
THE END
广告
喜欢就支持一下吧
点赞5 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情

    暂无评论内容