InnoDB redo

redo 是实现事务持久性的重要组成部分,innodb存储引擎中的redo文件在数据目录下,名字为ib_logfile0和ib_logfile1的文件,它以每次io为512字节为最小io写入redo文件中,redo文件在物理磁盘中顺序写入(这里我们大致可以认为是顺序的,其实不然,后续会详细展开讨论),其大小最大为512G,两个文件循环使用。其中使用最小io512字节也是为了迎合物理磁盘一次物理io512字节,这样redo写入就无需使用DBW就能保证写入成功。当数据发生变更都先变更记录会记录在logbuffer中,当达到一定的条件之后顺序的异步(ASYNC)写入redo file中,写入日志成功后数据才会进行数据刷脏(force log at commit),所以redo file的性能直接影响到整个数据库的整体写入性能。

写入redo触发条件

①事务提交时

②log buffer使用到1/2时

③log checkpoint时

④master thread每1s刷一次

⑤每次DML都会检查log buffer是否有足够的的空间来存储redo信息(预设的一个经验值)

group commit

由于每次写入redo都需要调用fsync(默认innodb_flush_log_at_trx_commit=1),即使再好的物理磁盘都承受不了频繁的io调动,这就给数据库带来了极大的性能隐患,innodb group commit就很好的解决了这一项隐患,简单来说就是每次commit 刷新多个事务日志写入redo log file中,在事务量大的系统中,group commit的效果尤为明显。下面详细讨论

事务commit时会进行两个阶段的操作

①修改内存中事务对应的信息,并将日志写入log buffer中。

②调用fsync将log buffer中的日志信息都写入 redo log file中,此过程是比较消耗时间的,在这个过程中其他事物是可以进行操作①的,同时可以多个事务同时进行②操作进行fsync刷新到redo log file。

然而对于mysql而言,innodb仅仅是一个存储引擎,他的所有操作是需要跟mysql进行统一协调的,当group commit引入后,commit的操作也需要跟MySQL进行协调,那么commit的整个过程就略显复杂了,为了保持binlog和redo的一致性,就引入了两阶段commit的概念,同时为了保证mysql数据库binlog日志写入顺序和innodb事务commit顺序一致性MySQL使用BLGC(binary log group commit)方式实现了在保证日志的一致性情况下还实现了binlog 和redo的group commit,其实现方式大致是这样的。

MySQL数据库进行commit时首先按照顺序将其放入一个队列中,队列中第一个事务为leader,其他事务为follower,leader控制着follower的行为。

①flush阶段,将每个事务binlog日志写入内存中

②sync阶段,将内存中的binlog写入binlog file中,若队列中有多个事务,只需要一次fsync操作完成binlog写入,即BLGC。

③commit阶段,leader根据顺序调用innodb事务commit,innodb按照以上讨论的group commit方式进行commit。

a.修改内存中事务对应的信息,并将日志写入log buffer中。

b.调用fsync将log buffer中的日志信息都写入 redo log file中

当一组事务进行commit阶段时,其他新的事务可以进行flush阶段,参数binlog_max_flush_queue_time用来控制flush阶段中等待事件,即完成一组事务commit后会等待一定的时间下一次日志接入在进入sync阶段,这样极大的发挥了group commit的作用,减少io,但是该参数默认为0,也推荐为0,主要就是因为生产系统日志写入是十分频繁的,根本不用我们可以的去“等待”

mysql hook机制_数据库

LSN

LSN(log sequence number)代表的是日志序列号,在innodb存储引擎中LSN占用8字节,并且单调递增

LSN代表redo log写入量:即若一个事务写入100个字节,那么LSN也会对应的增长100,

checkpoint的位置

page的版本:在每个page的头部FIL_PAGE_LSN记录该页的LSN(即最后刷新时LSN的大小),redo log记录每个页的日志,故通过该页的LSN来判断页是否需要进行恢复操作。

Log sequence number          17753977
Log flushed up to            17753977
Last checkpoint at           17753977

log sequence number表示当前LSN,log flush up to 表示刷新到redo log file的LSN,last checkpoint at表示刷新到磁盘的LSN,生产环境是不一致的。

innodb_flush_log_at_trx_commit

0:事务提交,但事务redo不写入redo log,在redo buffer中,等待master thread每秒刷新写入redo log --1s。

1:事务提交,事务redo fsync写入redo log,

2:事务提交,事务redo async写入redo log。

redo 结构

mysql hook机制_database_02

redo数据以log block形式存储在logbuffer中。log block也是有日志块头(log block header,12字节) 和块尾(log block tailer,8字节)两部分组成的。

其中log block header由四部分组成,

· LOG_BLOCK_HDR_NO:4字节,标记log block在redo buffer中的位置,递增并且循环使用。

· LOG_BLOCK_HDR_DATA_LEN:2字节,表示log block占用的空间大小,全部被写满时该值为0x200.

· LOG_BLOCK_FIRST_REC_GROUP:2字节,表示log block中第一个日志所在的偏移量,若与LOG_BLOCK_HDR_DATA_LEN相同则表示当日按blog block不包含新的日志。

mysql hook机制_mysql_03

由于事务T1的redo log占用792个字节,需要占用两个log block,左侧的 log block 中 LOG_BLOCK_FIRST_REC_GROUP为12(即log block中第一个日志开始的位置)。右侧的log block中,由于包含了之前事务T1的redo log,视图T2的日志才是log block中的第一个日志,故log block的LOG_BLOCK_FIRST_REC_GROUP为282(270+12)。

· LOG_BLOCK_CHECKPOINT_NO:4字节,表示 log block最后被写入时的检查点。

log block tailer只由一部分组成,其值和LOG_BLOCK_HSR_NO相同,同样占4个字节,并在函数log_block_init中被初始化

前面讨论到redo log buffer中redo信息是顺序的写入redo log file,这里我们可以大致那么认为,实际上每一个redo log file 是从第3kb块位置开始顺序写入的,后续log block总是写在redo log file 最后部分。这是因为第一个redo log file 前2kb部分不保存log block信息,在log group中其他的redo log file也保留这些空间,但是不保存相关信息,他是分成4个 512字节大小的块分别存放相关信息

log file header : 512字节,

checkpoint1: 512字节

空: 512字节,

checkpoint2: 512字节,

redo日志记录结构

innodb 存储引擎的存储管理是基于页的,redo日志自然也是这样,他的存储格式有着同样的头部格式

mysql hook机制_数据库_04

redo_log_type:redo的类型,占用1字节

space:使用表空间的ID,占用小于4字节

pace_no: 页的偏移量,压缩方式存储

redo_log_body: redo日志数据部分,recover时通过响应函数进行解析。

redo_log_body部分根据redo日志类型的不同会有不同的存储内容

mysql hook机制_数据库_05

redo恢复

innodb在启动时不管上次数据库是否正常关闭,都会尝试进行恢复操作,由于redo记录的是物理日志,故在实际恢复中要比binlog这种逻辑日志恢复起来快得多。在恢复过程中,innodb只需使用checkpoint的LSN作为起点读取redo log进行恢复