Author: jiasu.zzy
为保证数据库在意外崩溃或是断电重启之后依然保证一致性和持久性,MySQL InnoDB需要对每个page修改操作在磁盘上记录redo log,以便在崩溃恢复时重新应用内存中丢失的修改,恢复到崩溃前的状态。
redo log record的编码方式对数据库的性能是有一定影响的,表现在以下几点:
另外,编码方式也会影响代码的开发以及维护复杂度,现在流行的云原生数据库架构中redo日志具有核心地位(如Amazon Aurora将redo日志下沉到了存储层),合适的编码方式或许能简化一部分代码开发和维护工作。
在MySQL 8.0中,记录对某个page进行修改的redo log record里通常会包含一些大概率分布在特定范围内的数值,比如标识一个page的space_id和page_no,而针对这种数值,innodb设计了一种简单的编码规则来予以压缩存储。下面就先介绍几种常见的编码方式。
针对前缀连续出现0,以及前缀连续出现1,这两种情况的值,进行了压缩表示。
前缀连续出现0:
连续出现25个0,1字节:0nnnnnnn 表示0~127
连续出现18个0,2字节:10nnnnnn nnnnnnnn 表示128~16511
连续出现11个0,3字节:110nnnnn nnnnnnnn nnnnnnnn 表示16512~2113663
连续出现4个0,4字节: 1110nnnn nnnnnnnn nnnnnnnn nnnnnnnn 表示2113664~270549119
前缀连续出现1:
连续出现22个1,2字节:111110nn nnnnnnnn 表示0xFFFFFC00~0xFFFFFFFF
连续出现15个1,3字节:1111110n nnnnnnnn nnnnnnnn 表示0xFFFE0000~0xFFFFFBFF
连续出现8个1,4字节: 11111110 nnnnnnnn nnnnnnnn nnnnnnnn 表示0xFF000000~0xFFFDFFFF
其他情况,占用5字节:11110000 nnnnnnnn nnnnnnnn nnnnnnnn nnnnnnnn
具体代码见storage/innobase/include/mach0data.ic里的mach_write_compressed函数。这种压缩格式适合在比较小的值或者特别大的值频繁出现的情况下使用,space_id和page_no就正好是这样的值。
将一个64位的值分为两部分,高位的前32bit使用上述的32位压缩格式进行存储,占用1~5字节,低位的后32bit不压缩直接存储,占用4字节。总共占用字节数
具体代码见storage/innobase/include/mach0data.ic里的mach_u64_parse_compressed函数。这种格式适合在高位32位部分值特别小或特别大,而低位32位部分的值分布比较均匀的情况下使用。
将一个64位的值分为两部分,如果高位的前32bit都是0,那么直接写入低位32bit的压缩格式,占用1~5字节。
如果高位的32bit不全为0,则首个字节写入0xFF,之后写入高位32bit的压缩格式,共占用2~6字节;然后写入低位32bit的压缩格式,占用1~5字节,这种情况下总占用就是3~11字节。
具体代码见storage/innobase/include/mach0data.ic里的mach_u64_read_much_compressed函数。这种64位压缩格式比前一种适用范围窄一些,适合存储比较小的值。
下面介绍redo log record的编码格式。
redo log record有很多不同类型,首先每个record的第一个字节就是用来标识其类型的,目前MySQL 8.0版本中有五十多种redo record类型。
第一个字节:
和特定page相关联的redo record,在第一个字节之后跟着的是space_id和page_no,并且都是使用上文介绍的32位压缩格式进行编码。
紧接着space_id和page_no之后的,就是各个record类型自己的log record body部分,每个类型有自己特定的编码方式。总体格式如下图所示
当然也有一些redo log record类型不包含space_id和page_no,比如MLOG_TABLE_DYNAMIC_META类型,用于记录表的auto_inc值,其中的table_id、dynamic metadata version以及auto_inc数值都是使用的上文所述的64位高压缩率格式编码。
本文不对每个具体的redo log record编码以及实现进行分析,感兴趣的读者可以参考https://zhuanlan.zhihu.com/p/440476383
MariaDB从10.2版本之后便开始使用MySQL 5.7的InnoDB引擎,也继承了InnoDB的redo log record编码方式。不过从10.6版本之后MariaDB就对原本InnoDB的redo log record编码格式进行了比较大的改动,以期望得到更优的性能。
在https://jira.mariadb.org/browse/MDEV-12353中,分析了若干mysql innodb原有redo log格式导致的性能问题:
针对这些存在的问题,MariaDB 10.6版本对redo log record编码格式进行了比较大的改动,总结如下:
对于最经常使用的WRITE类型redo log record,后面跟着的是32位压缩格式的页内便宜offset(占用大小是1~3字节,因为page最大为16k)和要写入的数据内容
WRITE/MEMSET/MEMMOVE类型的redo record代表了对一个page纯粹物理上的改动,并不包含任何逻辑上的信息,对于某些修改操作,其占用的空间会比原本的逻辑编码方式要大。
以innodb中频繁出现的写undo操作为例:原本innodb在记录undo写入时是生成一条MLOG_UNDO_INSERT类型的redo log record,编码格式如下图所示
具体格式解析并应用的代码见MySQL 8.0的storage/innobase/trx/trx0rec.cc的trx_undo_parse_add_undo_rec函数,如下
/** Parses a redo log record of adding an undo log record.
@return end of log record or NULL */
byte *trx_undo_parse_add_undo_rec(byte *ptr, /*!< in: buffer */
byte *end_ptr, /*!< in: buffer end */
page_t *page) /*!< in: page or NULL */
{
ulint len;
byte *rec;
ulint first_free;
if (end_ptr < ptr + 2) {
return (nullptr);
}
len = mach_read_from_2(ptr);
ptr += 2;
if (end_ptr < ptr + len) {
return (nullptr);
}
if (page == nullptr) {
return (ptr + len);
}
first_free = mach_read_from_2(page + TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_FREE);
rec = page + first_free;
mach_write_to_2(rec, first_free + 4 + len);
mach_write_to_2(rec + 2 + len, first_free);
mach_write_to_2(page + TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_FREE,
first_free + 4 + len);
ut_memcpy(rec + 2, ptr, len);
return (ptr + len);
}
可以发现apply一条MLOG_UNDO_INSERT类型redo record本质上就是两件事情:
这条redo所占用的空间是1+1~5+1~5+2+length字节,即5~13+length字节
而如果纯粹使用非EXTENDED类型的物理编码方式,这条MLOG_UNDO_INSERT会被拆解成以下两条redo record:
总共占用空间为12~27+length字节,反而比之前innodb所使用的逻辑编码方式要大,所以mariadb使用了EXTENDED类型的UNDO_APPEND子类型,body部分的编码方式与之前的MLOG_UNDO_INSERT完全一致。其他的EXTENDED子类型具体见mariadb的storage/innobase/include/mtr0types.h
这里引用[https://jira.mariadb.org/browse/MDEV-12353]中的一张对比表格,第一列是原本innodb生成的redo log record大小,第二列是纯粹使用新的物理编码格式的大小,第三列是在引入EXTENDED子类型后占用的大小。
根据https://jira.mariadb.org/browse/MDEV-12353中针对update场景的性能测试,修改了redo log record编码后,crash recovery的性能可以提高大约15%
不仅是redo log record的编码方式,最新版本的mariadb还对原本innodb的redo log的文件格式进行了大幅度修改(参考https://jira.mariadb.org/browse/MDEV-14425),具体细节以后有时间会继续介绍。