背景
在MySQL5.7之前的版本中,每次崩溃恢复时都需要打开数据目录下所有的ibd文件,校验其有效性并内存中创建表空间链表。当表的数量特别多的时候, 会严重影响到崩溃恢复的性能。
为了解决这个问题,MySQL5.7版本中增加了新的日志类型MLOG_FILE_NAME及MLOG_CHECKPOINT。前者记录了被修改后的Ibd文件名,后者记录了最近一次checkpoint的LSN点。通过扫描Redo日志,InnoDB可以找到在最近一次checkpoint之后修改过的数据文件,这样在崩溃恢复时就无需打开所有的表空间。
但据我所知,这个特性至少引入了三个问题。以下简单为大家分享下
Log Parse Buffer溢出(bug#83245)
第一个问题是在做checkpoint时,会在一个mtr里将这期间修改的文件名记录到redo中,如果涉及的文件过多的话,就会导致一个mtr非常巨大,在崩溃恢复时,用于解析log的buffer(大小为2MB)会溢出。这个bug在MySQL5.7.18版本被修复。
为了避免这个问题,在做checkpoint写文件名时,如果已产生的log大小超过4个page size(LOG_CHECKPOINT_FREE_PER_THREAD), 就将当前日志提交掉并重新开启mtr(这中间不释放log_sys->mutex)
另一个修改是在崩溃恢复parse redo log的时候,如果一个log group中都是MLOG_FILE_NAME, 则直接推进recovered_offset/lsn,相当直接推进recover的点,这样在parse buffer里就无需存储整个mtr的日志。
详细修复补丁见这个commit
崩溃恢复的性能退化(bug#80788)
该特性带来的另外一个严重问题是崩溃恢复的性能退化,尤其是在表少,但需要恢复的日志量很大的时候。这是因为在崩溃恢复时最少扫描两次日志,最多要重复扫描三次,以下摘自commit log的解释
First scan: Scan all the redo logs from checkpoint lsn and process
only MLOG_FILE_* records during first scan. It scans till the
last MLOG_CHECKPOINT.
Second scan: Scan all redo logs from checkpoint lsn and add log
records to hash table. It verifies whether space id is having
corresponding MLOG_FILE_NAME record. If the hash table heap memory
is reached the threshold then stop adding records to hash but it
continues to scan till end of the redo log file.
Third scan: Scan all redo logs from checkpoint lsn and add log records
to hash table only if the tablespace exists. If the heap memory reached
the threshold then simultaneous scan and apply will happen.
我在report了bug后,很快官方就确认了,但直到5.7.19版本才fix了这个问题,对崩溃恢复有要求的同学最好尽快升级到这个版本。
简单看了下官方的修复,思路也比较简单,将第一次和第二次扫描合并,这样最少一次,最多两次扫描(buffer pool不够大的时候)。
更高的fil_system::mutex冲突(bug#85304)
由于需要在redo中记录修改的文件名,每次在做数据变更时,都会去调用函数mtr_t::set_named_space
,对于用户表空间,会进一步的调用函数mtr_t::lookup_user_space-> fil_space_get
通过space id来获得表空间的fil_space_t对象,这需要fil_system::mutex的保护。在高并发下可能导致比较激烈的锁冲突。
目前MySQL5.7还没有修复计划(估计也不会去修了..),但8.0版本已经将 WL#7142的工作整体删除了,并实现了另外一套机制来加速崩溃恢复(后面单独介绍)。而针对fil_system::mutex的通用场景冲突也在优化过程中。我们可以期待下MySQL8.0版本 :)