MySQL Innodb MTR源码解析

时间:2022-05-03
本文章向大家介绍MySQL Innodb MTR源码解析,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

最近看了下Mysql innodb源码MTR模块,了解源码能帮助DBA更熟悉数据库运行原理、更容易定位排查问题。那么什么是Mtr?Mtr究竟是用来做什么的?围绕几个问题我们来做一下深入研究。

一、什么是MTR?

Mtr即Mini-transaction的缩写,字面意思小事物,相对逻辑事物而言,我们把它称作物理事物。属于Innodb存储引擎的底层模块。主要用于锁和日志信息。

我们知道Innodb事物有四种特性:ACID即原子性、一致性、隔离性、持久性,通常称为逻辑事物。用来保证一致性和持久性的机制就是Mtr,即锁和日志,一旦事务提交,则其所做的修改会永久保存到数据库。

二、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参数控制是否必须启用该规则。

  • 在innodb中有两类日志:redo log和undo log。其中redo log是用来做数据异常恢复和数据库重启时页数据同步恢复的,redo log就是建立在Mtr基础上。数据库在执行事务时,通过Mtr产生redo log来保证事务的持久性。
  • 物理事务从名字来看是物理的,因为在innodb存储引擎中,只要是涉及到文件读取、修改等物理操作,都离不开Mtr,可以说Mtr是内存与文件之间的一个桥梁。
  • 可参考下图:

三、MTR工作方式

  • Mtr结构体初始化

物理事务既然被称为事务,那它同样有事务的开始与提交,在innodb中,物理事务的开始其实就是对物理事务的结构体mtr_t的初始化,在mtr0mtr.h文件中,其中包括下面一些成员:

这个结构体中dyn_array_t是两个动态数组:

  1. 成员state表示mtr的状态,包括三种状态;
  2. 成员memo用来存储所有这个物理事务用到(访问)的页面,这些页面都是被所属的物理事务上了锁的(读锁或者写锁,某些时候会不上锁);
  3. 成员log用来存储这个物理事务在访问修改数据页面的过程中产生的所有日志,这个日志就是数据库中经常说到的重做(redo log)日志;
  4. 成员n_log_recs表示这个物理事务操作的页个数;
  5. log_mode表示这个物理事务的日志模式,包括MTR_LOG_ALL(写日志)、MTR_LOG_NONE(不写日志)等;
  6. LSN两个成员表示这个物理事务开始前的LSN及这个物理事务提交后产生的新的LSN。
  • mtr_t中成员memo

是个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中的锁的控制权

  • mt_t中的成员log

是也是一个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。

以下是一个比较具体的示意图:

  • mt_t中的成员modifications

是标识是否有page的数据改动,如果有,在mtr_commit调用时会先将mtr->log刷盘,然后释放mtr所有的所控制权。日志一定会在mtr结束时刷盘,这符合Force-log-at-commit的规则。日志写入调用的是log_write_low这个函数。

  • 其中memo和log的保存形式如下:
  • Mtr抽象流程图及步骤
  1. 首先在系统将一个页面载入到缓冲区的时候,需要新开始一个(mtr_start)或者一个已经开始的物理事务,载入时需要指定页的获取方式,比如是用来读取的还是用来修改的,这样会影响物理事务对这个页的上锁情况,如果用来修改,则上X锁,否则上S锁(当然还可以指定不上锁)。在确定了获取方式、这个页的表空间ID及页号之后,就可以通过函数buf_page_t来获取指定页面了。
  1. 当找到相应页后,物理事务就要对它上指定的锁,此时需要对这个页的上锁情况进行检查,一个页的上锁情况是在结构体buf_block_struct中的lock中体现的,此时如果这个页还没有上锁,则这个物理事务直接对其上锁,否则还需要考虑两个锁的兼容性,只有两个锁都是共享锁(S)的情况下才是可以上锁成功的,否则需要等待。当上锁成功后,物理事务会将这个页的内存结构存储到上面提到的memo动态数组中。然后这个物理事务就可以访问这个页了。mtr_start再mtr0mtr.ic文件中。
  1. 物理事务对页面的访问包括两种操作,一种是读,另一种是写,读就是简单读取其指定页面内偏移及长度的数据;写则是指定从某一偏移开始写入指定长度的新数据,同时如果这个物理事务是写日志的(MTR_LOG_ALL),此时还需要对刚才的写操作记下日志,这里的日志就逻辑事务中提到的redo日志。写下相应的日志之后,同样将其存储到上面的log动态数组中,同时要将上面结构体中的n_log_recs自增,维护这个物理事务的日志计数值。
  2. 物理事务的读写过程主要就是上面介绍的内容,其最重要的是它的提交过程。物理事务的提交是通过mtr_commit来实现的,物理事务的提交主要是将所有这个物理事务产生的日志写入到innodb的日志系统的日志缓冲区中,然后等待srv_master_thread线程定时将日志系统的日志缓冲区中的日志数据刷到日志文件中。

四、确保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存储引擎》