最近看了下Mysql innodb源码MTR模块,了解源码能帮助DBA更熟悉数据库运行原理、更容易定位排查问题。那么什么是Mtr?Mtr究竟是用来做什么的?围绕几个问题我们来做一下深入研究。
一、什么是MTR?
Mtr即Mini-transaction的缩写,字面意思小事物,相对逻辑事物而言,我们把它称作物理事物。属于Innodb存储引擎的底层模块。主要用于锁和日志信息。
我们知道Innodb事物有四种特性:ACID即原子性、一致性、隔离性、持久性,通常称为逻辑事物。用来保证一致性和持久性的机制就是Mtr,即锁和日志,一旦事务提交,则其所做的修改会永久保存到数据库。
二、MTR原理
1) The FIX Rules:
修改或访问一个页需要获得该页的x-latch或s-latch,直到修改或者访问该页的操作完成。每个页都有个buf_block_t的对象:
struct buf_block_struct{
......
rw_lock_t lock; #对页的latch操作
ulint buf_fix_count; #有多少个操作fix该页
......
}
其中lock实现对页的latch操作,buf_fix_count表示有多个操作fix该页。多个事物读取一个页时,该变量就会自增。当一个页根据LRU算法从缓冲池中替换时,该变量必须为0。
2) Write-Ahead Log:
如果一个页操作在写入到持久设备时,必须先将内存中小于该页LSN的日志写入到持久化设备中。
3) Force-log-at-commit:
前面两个主要是为了保证事物的一致性,而事物的持久性还需要这个规则进一步加强。一个事物可以涉及多个页,当事物提交时,产生所有的Mtr日志必须刷到持久设备中。通过innodb_flush_log_at_trx_commit参数控制是否必须启用该规则。
三、MTR工作方式
物理事务既然被称为事务,那它同样有事务的开始与提交,在innodb中,物理事务的开始其实就是对物理事务的结构体mtr_t的初始化,在mtr0mtr.h文件中,其中包括下面一些成员:
这个结构体中dyn_array_t是两个动态数组:
是个latch持有状态的数组列表,采用的是dyn_array_t的动态内存结构来保存的,每个单元存储的是mtr_memo_slot_t这样的结构。定义如下:
latchtype如下:
MTR_MEMO_PAGE_S_FIX | /rw_locks-latch/ |
---|---|
MTR_MEMO_PAGE_X_FIX | /rw_lockx-latch/ |
MTR_MEMO_BUF_FIX | /buf_block_t/ |
MTR_MEMO_S_LOCK | /rw_lock s-latch/ |
MTR_MEMO_X_LOCK | /rw_lock x-latch/ |
object是latch的对象,可以是rw_lock_t对象,也可以是buf_block_t对象。
memo的latch管理接口:
mtr_memo_push | 获得一个latch,并将状态信息存入mtr memo当中 |
---|---|
mtr_memo_slot_t | 保存latch内容 |
mtr_release_s_latch_at_savepoint | 释放memo偏移savepoint的slot锁状态 |
mtr_memo_contains | 判断锁对象是否在memo当中 |
mtr_memo_slot_release | 释放slot锁的控制权 |
mtr_memo_pop_all | 释放所有memo中的锁的控制权 |
是也是一个dyn_array_t动态结构的内存,用来保存mtr产生的日志信息。日志的写入是通过mtr0log.h来写入的。
重做日志虽然有很多种类型,但重做的日志格式是统一的,日志格式是有日志头和日志体组成,日志头信息是由type、space和page no组成,由mlog_write_initial_log_record_fast函数写入到mtr_t的log中的。log body的数据写入是通过mtr0log.h中的日志写入方法进行写入的,每写入一条操作日志,n_log_recs会加1。
以下是一个比较具体的示意图:
是标识是否有page的数据改动,如果有,在mtr_commit调用时会先将mtr->log刷盘,然后释放mtr所有的所控制权。日志一定会在mtr结束时刷盘,这符合Force-log-at-commit的规则。日志写入调用的是log_write_low这个函数。
四、确保MTR完整性
上面已经提过,物理事务和逻辑事务一样,也是可以保证数据库操作的完整性的,一般说来,一个操作必须要在一个物理事务中完成,也就是指要么这个操作已经完成,要么什么也没有做,否则有可能造成数据不完整的问题,因为在数据库系统做redo操作时是以一个物理事务为单位做的,如果一个物理事务的日志是不完整的,则它对应的所有日志都不会重做。那么如何辨别一个物理事务是否完整呢?
这个问题是在物理事务提交时用了个很巧妙的方法保证了,在提交前,如果发现这个物理事务有日志,则在日志最后再写一些特殊的日志,这些特殊的日志就是一个物理事务结束的标志,那么提交时一起将这些特殊的日志写入,在重做时如果当前这一批日志信息最后面存在这个标志,则说明这些日志是完整的,否则就是不完整的,则不会重做。
物理事务提交时还有一项很重要的工作就是处理上面结构体中动态数组memo中的内容,现在都已经知道这个数组中存储的是这个物理事务所有访问过的页面,并且都已经上了锁,那么在它提交时,如果发现这些页面中有已经被修改过的,则这些页面就成为了脏页,这些脏页需要被加入到innodb的buffer缓冲区中的更新链表中(详细了解BUFFER),当然如果已经在更新链中,则直接跳过(不能重复加入),svr_master_thread线程会定时检查这个链表,将一定数目的脏页刷到磁盘中,加入之后还需要将这个页面上的锁释放掉,表示这个页面已经处理完成;如果页面没有被修改,或者只是用来读取数据的,则只需要直接将其共享锁(S锁)释放掉即可。
五、总结
上面的内容就是Mtr的一个完整的讲述,因为它是比较底层的一个模块,牵扯的东西比较多,这里重点讲述了物理事务的意义、操作原理、与BUFFER系统的关联、日志的产生等内容。
Mtr是innodb对ACID中的持久性的最小保证单元,所有涉及到事务执行、页数据刷盘、redo log数据恢复等都需要进行mini transaction的构造和执行。几乎所有的模块都涉及到minitransaction,例如:btree、page、事务、inser tbuffer、redo-log等,对mini transcaion的理解不能孤立的去看源码,应该结合redo log、page等相关的代码了解。它是理解innodb工作原理的基石。
六、MTR涉及的模块文件
.h头文件统一放在include目录下,全局变量定义,宏定义、函数说明等 | .ic也在include目录下,这类文件为每个模块定义了内联函数 | .c\.cc标准源文件,在各自模块目录下,具体实现功能 |
---|---|---|
mtr0mtr.h | mtr0mtr.ic | page0cur.c |
mtr0log.h | mtr0log.ic | mtr0mtr.cc |
mtr0types.h | mtr0log.cc | |
mem0mem.h | ||
dyn0dyn.h | ||
buf0types.h | ||
sync0rw.h | ||
ut0byte.h | ||
page0types.h | ||
dict0types.h | ||
buf0buf.h | ||
mach0data.h |
参考:《MYSQL内核:INNODB存储引擎》