MySQL 5.7中 Log Sys锁冲突比较大,MySQL 8.0对InnoDB Log Sys进行了重构。
我们先描述下5.7的 Log Sys看看锁冲突,然后再介绍8.0的方案以及部分代码实现细节。
mtr
mtr 表示 mini-transaction,表示操作的一个最小原子单元,比数据库事务概念要更小。比如一个事务可能插入两行数据,但每插入一行都可能触发B-Tree的叶子分裂,页面的分裂操作涉及多个页面,这些页面的修改必须保持原子(不能发生分裂的第一个页面生效但第二个页面没生效的情况)。这种原子性就是靠mtr来实现的。
mtr做一次修改,通常涉及两个主要的步骤,即对修改页面加锁,产生修改变更数据。mtr在提交的时候,需要相应地进行两个操作,一个是将页面挂在flush list上,一个是将变更写入redo log中。刷脏逻辑会从flush list上按从老到新进行刷脏,已经刷脏完成的页面对应的redo就可以不需要了(日志内容可以回收或者循环覆盖使用,重启不需要回放这部分内容)。
MySQL 5.7 Log Sys并发冲突
基于此,5.7中保证了flush list在前的页面对应的变更修改在redo log中也在前面。具体保证方式是,采用两把锁“接力”,log_sys_t::mutex和log_sys_t::flush_order_mutex,线程首先拿到log_sys_t::mutex,,然后挂flush list,之后拿到log_sys_t::flush_order_mutex后释放log_sys_t::mutex,然后将变更写入redo log buffer。如参考链接中的图示。
!http://loopjump.com/wp-content/uploads/2022/06/1-1-300x201.png
显然,这会带来严重的并发冲突,因此8.0重构了这一块内容。
MySQL 8.0 Scalable Log Sys 介绍
首先看下8.0中做到了什么效果呢?
- 不同线程可以并发写redo log buffer
- 挂flush list也不必完全按照redo log buffer的顺序来,而是一定程度内允许乱序
- redo log的writer和flusher采用独立线程
第一条其实有篇类似的论文可以参考Aether: A Scalable Approach to Logging。
不同线程在将线程本地事务的修改数据写到全局的redo log buffer的时候,将自己的数据字节数目原子地加到当前redo log的offset(offset为原子变量),这样相当于每个线程先抢占了redo log buffer中自己的数据对应的一段,然后这些线程就可以并发地向各自的buffer段进行copy。
!http://loopjump.com/wp-content/uploads/2022/06/2-1.png
!http://loopjump.com/wp-content/uploads/2022/06/3-1.png
但真正将redo log buffer写入文件的时候,只能写到连续已copy的位置,不能包含空洞。所以每个线程在copy完之后,要向一个全局数据结构Finished Writes通知自己的这段已经完成。独立的log writer线程负责检查连续已完成copy的位置,将这部分连续的buffer写入文件。另外一个独立线程log flusher执行真正的flush操作(fsync)。
另外一块是 flush_list 的问题。
redo log buffer并发copy,对应的flush list的顺序怎么保证呢?不太准确地说,答案是不保证。
注意到,一个脏页被刷盘后,如果重启,redo log中这个脏页上的哪些日志需要回放哪些不需要回放实际上是比较容易判断的,因为页面头上会记录这个页面最后一次修改对应的redo log的lsn(offset的等价概念,除去了file header等等)。小于这个lsn的redo log record都不需要回放,大于的都需要回放。那读者可能会疑问,这样的话,5.7为什么要通过两把接力锁呢?实际上5.7也不需要,但它就实现成那个样子了。
不过,如果允许完全乱序挂flush list,那么重启的时候,怎么获得重启回放位点呢?8.0的方法是不完全乱序,只允许一定范围内的乱序,即最老未写redo log buffer的位置 + L之后的flush list都等待。这样重启回退L作为重启回放位点就可以了。
因为log_writer和log_flusher是独立线程,所以一个mtr提交的时候,要等log_writer/log_flusher完成对应commit lsn的写盘/flush操作,所以mtr提交的时候会检查当前已完成写盘/flush的位置和自己的commit lsn的大小,这中间会涉及等待和条件变量等,8.0里面还增加了独立的唤醒线程log_write_notifier和log_flush_notifier。
MySQL 8.0 Scalable Log Sys 代码实现
1 | struct log_t |
扫描二维码,分享此文章