1. 可重复读的疑惑
事务的隔离级别定义了数据库系统中一个事务中操作的结果在何时以何种方式对其他并发事务操作可见。
SQL标准中定义的4种隔离级别(未提交读、已提交读、可重复读、可串行化)中,只有2种(已提交读-RC、可重复读-RR)会被使用,RC是为了解决“脏读”问题 ,RR则是为了解决“不可重复读”问题。
其中,“脏读”问题一般是不可容忍的,那么“不可重复读”问题呢?
很多时候,所谓的”不可重复读“问题其实是完全可接受的。如果“不可重复读”是一个伪问题,那么MySQL中的”可重复读“这种隔离级别存在的实际意义又是什么呢?
以个人的使用经验来看,MySQL的”可重复读“这种隔离级别带来的是弊大于利,因为它具备的”间隙锁“及”事务内同一份快照读“的特性,很容易给业务应用引入不可控性,带来一定的技术风险。2. 间隙锁的副作用
间隙锁是MySQL实现”可重复读“隔离级别的一个重要机制。
间隙锁属于排它锁,它会把查询sql中最靠近检索条件的左右两边的数据间隙锁住,防止其它事务在这个间隙内插入、修改、删除数据,从而保证该事务内,任何时候以相同检索条件读取的数据都是相同,也即保证可重复读。
关于间隙锁的更多信息,网上已有很多文章作了非常详细的介绍,本文不再赘述,这里只稍微谈下间隙锁带来的负面影响。
间隙锁的触发条件:
事务隔离级别为RR。因为间隙锁只有在事务隔离级别RR中才会产生,隔离级别级别是RC的话,间隙锁将会失效
显式加锁。比如使用了类似于select...for update这样的加锁语句
查询条件必须有索引:
若查询条件走唯一索引:只有锁住多条记录或者一条不存在的记录的时候,才会产生间隙锁;如果查询单条存在的记录,不会产生间隙锁
若查询条件走普通索引:不管是锁住单条,还是多条记录,都会产生间隙锁
在高并发下,由于间隙被锁住,导致需要往间隙内插入、删除、修改数据的并发线程必须等待,会带来一定性能问题,并且最终锁影响的范围可能远远超过我们想要操作的数据。
如果实际业务场景中无需锁住数据间隙,建议关闭间隙锁,或者将MySQL隔离级别由RR改为RC,否则会带来无谓的性能开销,甚至会引发死锁,影响业务运行。
注:文末的附录例一已经过本人测试验证,可复现出间隙锁给并发事务带来的阻塞等待。
3. 事务内同一份快照读
RR与RC下的普通读都是快照读,但两者的快照读有所不同:
RC下,事务内每次都是读最新版本的快照数据
RR下,事务内每次都是读同一版本的快照数据(即首次read时的版本)
注:上面提到的普通读是指除了如下2种之外的select都是普通读
select * from table where ... for update;select * from table where ... lock in share mode;
RR下,事务会以第一次普通读时快照数据为准,该事务后续其他的普通读都是读的该份快照数据,也即事务内是同一份快照读。
但在现实中,一个事务里重复同一条sql再次查询的场景极低,且出于性能的考虑,一般也会尽量避免在同一事务内对同一数据进行多次查询。 因此,RR下所谓的事务内同一份快照读意义并不大。而且,如果理解不到位,还会给应用带来一定的困扰,具体可详见文末的附录例二。
4. 最后的话
综上所述,目前MySQL中的可重复读并无多少实际意义,或者说,其理论意义远大于工程意义 。MySQL隔离级别使用RR反而很容易给应用引入一些不可控性,增加系统的性能风险,甚至影响业务运行。总之,就目前而言,对于MySQL,隔离级别请使用RC,不要使用RR。
在很多实际应用场景中,”不可重复读“是完全可以接受的,本人水平及视野有限,目前想不出,到底有什么场景必须使用“可重复读”作为MySQL隔离级别。当然了,如果哪位小伙伴确实有遇到需要使用可重复读的场景,比如该场景必须要锁住数据间隙、必须要保证事务内是同一份快照读,还请一定要告诉我是什么场景。
(个人邮箱:tom_kang@qq.com)
5. 附录
以下例子均已经过了本人的测试验证(MySQL8.0.19)。
一、间隙锁实操示例
唯一索引的间隙锁例子
普通索引的间隙锁例子
具体例子详见如下链接:
https://m5cx5l1kq0.feishu.cn/docs/doccn5TAc8HxyTVRemm2opkBIRb
二、RR快照读实操示例
具体例子可参考如下链接:
https://m5cx5l1kq0.feishu.cn/docs/doccnEsJPB8vrS9RFDvRr2DsR6b
- 往期回顾 -热点账户系列-异步入账方案