MySQL 8.0 InnoDB无锁化设计的日志系统

作者介绍
张永翔,现任网易云RDS开发,持续关注MySQL及数据库运维领域,擅长MySQL运维,知乎ID:雁南归 。
MySQL 8.0中一个重要的新特性是对Redo Log子系统的重构,通过引入两个新的数据结构recent_written和recent_closed,移除了之前的两个热点锁:log_sys_t::mutex和log_sys_t::flush_order_mutex 。
这种无锁化的重构使得不同的线程在写入redo_log_buffer时得以并行写入,但因此带来了log_buffer不再按LSN增长的顺序写入的问题,以及flush_list中的脏页不再严格保证LSN的递增顺序问题 。
本文将介绍MySQL 8.0中对log_buffer相关代码的重构,并介绍并发写log_buffer引入问题的解决办法 。
一、MySQL Redo Log系统概述
Redo Log又被称为WAL ( Write Ahead Log),是InnoDB存储引擎实现事务持久性的关键 。
在InnoDB存储引擎中,事务执行过程被分割成一个个MTR (Mini TRansaction),每个MTR在执行过程中对数据页的更改会产生对应的日志,这个日志就是Redo Log 。事务在提交时,只要保证Redo Log被持久化,就可以保证事务的持久化 。
由于Redo Log在持久化过程中顺序写文件的特性,使得持久化Redo Log的代价要远远小于持久化数据页,因此通常情况下,数据页的持久化要远落后于Redo Log 。
每个Redo Log都有一个对应的序号LSN (Log Sequence Number),同时数据页上也会记录修改了该数据页的Redo Log的LSN,当数据页持久化到磁盘上时,就不再需要这个数据页记录的LSN之前的Redo日志,这个LSN被称作Checkpoint 。
当做故障恢复的时候,只需要将Checkpoint之后的Redo Log重新应用一遍,便可得到实例Crash之前未持久化的全部数据页 。
InnoDB存储引擎在内存中维护了一个全局的Redo Log Buffer用以缓存对Redo Log的修改,mtr在提交的时候,会将mtr执行过程中产生的本地日志copy到全局Redo Log Buffer中,并将mtr执行过程中修改的数据页(被称做脏页dirty page)加入到一个全局的队列中flush list 。
InnoDB存储引擎会根据不同的策略将Redo Log Buffer中的日志落盘,或将flush list中的脏页刷盘并推进Checkpoint 。
在脏页落盘以及Checkpoint推进的过程中,需要严格保证Redo日志先落盘再刷脏页的顺序,在MySQL 8之前,InnoDB存储引擎严格的保证MTR写入Redo Log Buffer的顺序是按照LSN递增的顺序,以及flush list中的脏页按LSN递增顺序排序 。
在多线程并发写入Redo Log Buffer及flush list时,这一约束是通过两个全局锁log_sys_t::mutex和log_sys_t::flush_order_mutex实现的 。
二、MySQL 5.7中MTR的提交过程
在MySQL 5.7中,Redo Log写入全局的Redo Log Buffer以及将脏页添加到flush list的操作均在mtr的提交阶段中完成,简化后的代码为:

MySQL 8.0 InnoDB无锁化设计的日志系统

文章插图
MySQL官方博客中有一张图可以很好的展示了这个过程:
MySQL 8.0 InnoDB无锁化设计的日志系统

文章插图
三、MySQL 8中的无锁化设计
从上面的代码中可以看到,在有多个MTR并发提交的时候,实际在这些MTR是串行的完成从本地日志Copy redo到全局Redo Log Buffer以及添加Dirty Page到Flush list的 。这里的串行操作就是整个MTR 提交过程的瓶颈,如果这里可以改成并行,想必可以提高MTR的提交效率 。
但是串行化的提交可以严格保证Redo Log的连续性以及flush list中Page修改LSN的递增,这两个约束使得将Redo Log和脏页刷入磁盘的行为很简单 。只要按顺序将Redo Log Buffer中的内容写入文件,以及按flush list的顺序将脏页刷入表空间,并推进Checkpoint即可 。
当MTR不再以串行的方式提交的时候,会导致以下问题需要解决:
  • MTR串行的copy本地日志到全局Redo Log Buffer可以保证每个MTR的日志在Redo Log Buffer中都是连续的不会分割 。当并行copy日志的时候,需要有额外的手段保证mtr的日志copy到Redo Log Buffer后仍然连续 。MySQL 8.0中使用一个全局的原子变量log_t::sn在copy数据前为MTR在Redo Log Buffer中预留好需要的位置,这样并行copy数据到Redo Log Buffer时就不会相互干扰 。
  • 由于多个MTR并行copy数据到Redo Log Buffer,那必然会有一些MTR copy的快一些,有些MTR copy的比较慢,这时候Redo Log Buffer中可能会有空洞,那么就需要一种方法来确定Redo Log Buffer中的哪些内容可以写入文件 。MySQL 8.0中引入了新的数据结构Link_buf解决了这个问题 。

MySQL 8.0 InnoDB无锁化设计的日志系统

文章插图