mini transaction 的信息保存在结构体 mtr_t 中,结构体成员描述详见前文,其中m_memo和m_log最为重要。本文的代码参照mysql 8.0.23。 ​

m_memo管理mtr持有的锁信息。对于持有的page锁,还要保留page指针,这是为了在commit时,将修改的脏页加入flush list中。 ​

m_log保存mtr修改操作对应的redo日志。在commit时,将redo日志一起拷贝到log_sys模块的公共日志buffer中。 ​

而mtr的使用方式如下:

我们重点看下mtr_t中m_memo和m_log成员的实现。m_memo和m_log都是mtr_buf_t类型的对象,mtr_buf_t是由一个双向链表组成的动态buffer,每个元素是512B大小的buffer(512B刚好匹配一个log block大小)。随着mtr_buf_t存储的数据的增加,它会自动生成新的512B的buffer,并加入双向链表中。 ​

m_log使用动态buffer的方式是把日志类型、space id、page no、以及具体的操作信息加入动态buffer。mlog_open:预分配待写入的日志空间,若空间不够,则增加新的buffer到动态buffer中。 mlog_write_initial_log_record_fast:写入日志类型、space id、page no,且m_n_log_recs加1。

mlog_close:更新最终的日志大小m_size ​

在开启一个mini transaction时,会初始化mtr对象中的m_log和m_memo成员,设置m_state为active。

公共log buffer是按照log block格式存储的(包含12B的header和4B的trailer,详见中的日志块结构),每个log block大小为512B,并且持久化时以512B进行对齐。每个log block中能存储日志内容的空间为512-12-4=496B。 ​

公共log buffer有个原子变量log.sn,其统计的是公共buffer中曾经存储过的日志内容的大小。通过sn可以很容易计算出对应的lsn,其统计的是公共buffer中曾经存储过的以log block格式的日志量的大小。 lsn = (sn / 496 * 512 + sn % 512 + 12)

公共log buffer是个循环buffer,其中有三个重要的位点log.write_lsn,log.sn对应的lsn,log.buf_limit_sn对应的lsn。其中log.write_lsn表示已写入磁盘的日志位点(不要求flush),log.sn对应的lsn表示已占位待拷贝的日志位点,log.buf_limit_sn对应的lsn表示可以占位的最大日志位点。满足log.write_lsn <= log.sn对应的lsn <= log.buf_limit_sn对应的lsn。 ​

将m_log中的日志写入公共log buffer:

  • 根据日志数m_n_log_recs是否为1,来判断是single log还是multiple log。对于single log,在日志的开头的日志类型字段中增加MLOG_SINGLE_REC_FLAG。而对于multiple log,在日志结尾增加1B的MLOG_MULTI_REC_END。
  • 在往已占位的日志空间中拷贝日志前,有以下两种情况需要等待:
    • 若当前的log.sn位点被SN_LOCKED锁定,则要等待log.sn_locked 超过占位前的log.sn。当公共log buffer需要在线变更大小的时候,会进行SN_LOCKED加锁。
    • 若日志写入速度过快,来不及写磁盘,就会把log buffer占满,这时需要阻塞等待日志的写磁盘。
  • 将m_log动态buffer拷贝到公共log buffer,是按照512B大小的buffer粒度进行拷贝的:
    • 若写到log buffer的结尾(默认大小为16M),要继续转向log buffer开头继续拷贝。由于log buffer大小是log block的倍数,所以这里不需要再次做截断。
    • 每个buffer拷贝完成后触发一次log.recent_written的Link_buf更新(详见前文),log.recent_written记录完成拷贝的最大连续日志的lsn
  • 当m_log日志都写完,要检查已写入的日志是否横跨log block,若横跨了,则要在结尾的log block的header的LOG_BLOCK_FIRST_REC_GROUP字段中标识新mtr的位点end_lsn。

将m_memo中的加锁并且发生修改的脏page加入flush list:

  • 遍历m_memo动态buffer中的每个buffer中的每个锁对象mtr_memo_slot_t
  • 触发一次log.recent_closed的Link_buf更新,log.recent_closed记录添加到flush list的最大连续日志的lsn