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
广告
暂无评论内容