MySQL 默认隔离级别是 RR,为什么阿里这种大厂会改成 RC?
我之前写过一篇文章《为什么MySQL选择REPEATABLE READ作为默认隔离级别?》介绍过 MySQL 的默认隔离级别是 Repeatable Reads 以及背后的原因。
主要是因为 MySQL 在主从复制的过程是通过 bin log 进行数据同步的,而 MySQL 早期只有 statement 这种 bin log 格式,这种格式下,bin log 记录的是 SQL 语句的原文。
当出现事务乱序的时候,就会导致备库在 SQL 回放之后,结果和主库内容不一致。
为了解决这个问题,MySQL 采用了 Repetable Read 这种隔离级别,因为在 RR 中,会在更新数据的时候增加记录锁的同时增加间隙锁。可以避免这种情况的发生。
关于 MySQL 的加锁方式及加锁原则,可以参考我写的另外一篇《求你了,别再说数据库锁的只是索引了!》,这里就不再赘述了。
在我知道 MySQL 默认隔离级别是 RR 后,很长一段时间都以为应该不会有人去修改这个默认配置。
但是直到有一天,我们线上发生了一次死锁的问题,我在排查的过程中,才发现我们的数据库用的隔离级别没有使用默认的 RR,而是修改成了 Read Committed 。(关于那次死锁排查过程,可以参考:一次数据库的死锁问题排查过程)
大家可以通过这个命令查看数据库当前的隔离级别:
那么,这里不禁就有疑问了,为啥阿里要把这个数据库隔离级别修改成 RC 呢,背后有什么思考吗?
RR 和 RC 的区别
想要搞清楚这个问题,我们需要先弄清楚 RR 和 RC 的区别,分析下各自的优缺点。
一致性读
一致性读,又称为快照读。快照即当前行数据之前的历史版本。快照读就是使用快照信息显示基于某个时间点的查询结果,而不考虑与此同时运行的其他事务所执行的更改。
在 MySQL 中,只有 READ COMMITTED 和 REPEATABLE READ 这两种事务隔离级别才会使用一致性读。
在 RC 中,每次读取都会重新生成一个快照,总是读取行的最新版本。
在 RR 中,快照会在事务中第一次 SELECT 语句执行时生成,只有在本事务中对数据进行更改才会更新快照。
在数据库的 RC 这种隔离级别中,还支持"半一致读" ,一条 update 语句,如果 where 条件匹配到的记录已经加锁,那么 InnoDB 会返回记录最近提交的版本,由 MySQL 上层判断此是否需要真的加锁。
锁机制
数据库的锁,在不同的事务隔离级别下,是采用了不同的机制的。在 MySQL 中,有三种类型的锁,分别是 Record Lock、Gap Lock 和 Next-Key Lock。
Record Lock 表示记录锁,锁的是索引记录。
Gap Lock 是间隙锁,锁的是索引记录之间的间隙。
Next-Key Lock 是 Record Lock 和 Gap Lock 的组合,同时锁索引记录和间隙。他的范围是左开右闭的。
在 RC 中,只会对索引增加 Record Lock,不会添加 Gap Lock 和 Next-Key Lock。
在 RR 中,为了解决幻读的问题,在支持 Record Lock 的同时,还支持 Gap Lock 和 Next-Key Lock;
主从同步
在数据主从同步时,不同格式的 binlog 也对事务隔离级别有要求。
MySQL 的 binlog 主要支持三种格式,分别是 statement、row 以及 mixed,但是,RC 隔离级别只支持 row 格式的 binlog。如果指定了 mixed 作为 binlog 格式,那么如果使用 RC,服务器会自动使用基于 row 格式的日志记录。
而 RR 的隔离级别同时支持 statement、row 以及 mixed 三种。
为什么互联网公司选择使用 RC
提升并发
互联网公司和传统企业最大的区别是什么?
高并发!
没错,互联网业务的并发度比传统企业要高处很多。2020 年双十一当天,订单创建峰值达到 58.3 万笔/秒。
很多人问,要怎么做才能扛得住这么大的并发量。其实,这背后的优化多到几个小时都讲不完,因为要做的、可以做的事情实在是太多了。
而有一个和我们今天这篇文章有关的优化,那就是通过修改数据库的隔离级别来提升并发度。
为什么 RC 比 RR 的并发度要好呢?
首先,RC 在加锁的过程中,是不需要添加 Gap Lock 和 Next-Key Lock 的,只对要修改的记录添加行级锁就行了。
这就使得并发度要比 RR 高很多。
另外,因为 RC 还支持"半一致读",可以大大的减少了更新语句时行锁的冲突;对于不满足更新条件的记录,可以提前释放锁,提升并发度。
减少死锁
因为 RR 这种事务隔离级别会增加 Gap Lock 和 Next-Key Lock,这就使得锁的粒度变大,那么就会使得死锁的概率增大。
死锁:一个事务锁住了表 A,然后又访问表 B;另一个事务锁住了表 B,然后企图访问表 A;这时就会互相等待对方释放锁,就导致了死锁。
总结
本文介绍了一些 MySQL 数据库的 RR 和 RC 两种事务隔离级别。他们主要在加锁机制、主从同步以及一致性读方面存在一些差异。
而很多大厂,为了提升并发度和降低死锁发生的概率,会把数据库的隔离级别从默认的 RR 调整成 RC。
当然,这样做也不是完全没有问题,首先使用 RC 之后,就需要自己解决幻读的问题,这个其实还好,很多时候幻读问题其实是可以忽略的,或者可以用其他手段解决。
还有就是使用 RC 的时候,不能使用 statement 格式的 binlog,这种影响其实可以忽略不计了,因为 MySQL 是在 5.1.5 版本开始支持 row 的、在 5.1.8 版本中开始支持 mixed,后面这两种可以代替 statement 格式。
所有的技术方案的选择,都是一种权衡的艺术!
参考资料: Transaction Isolation Levels
版权声明: 本文为 InfoQ 作者【Hollis】的原创文章。
原文链接:【http://xie.infoq.cn/article/c7adc542d565d8e5e5764dcdd】。文章转载请联系作者。
评论