MySQL系列:innodb源码分析之重做日志结构(一)

2015-01-23 22:04:52 · 作者: · 浏览: 12

?

1.LSN

在innodb中的重做日志系统中,定义一个LSN序号,其代表的意思是日志序号。LSN在引擎中定义的是一个dulint_t类型值,相当于uint64_t,关于dulint_t的定义如下:

?

?

typedef struct dulint_struct
{
     ulint high;     /* most significant 32 bits */
     ulint low;       /* least significant 32 bits */
}dulint_t;
LSN真正的含义是储存引擎向重做日志系统写入的日志量(字节数),这个日志量包括写入的日志字节 + block_header_size + block_tailer_size。LSN的初始化值是:LOG_START_LSN(相当于8192),在调用日志写入函数LSN就一直随着写入的日志长度增加,具体看:

?

?

void log_write_low(byte* str, ulint str_len)
{
 log_t* log = log_sys;
. . .
part_loop:
 /*计算part length*/
 data_len = log->buf_free % OS_FILE_LOG_BLOCK_SIZE + str_len;
.  .  . 
 /*将日志内容拷贝到log buffer*/
 ut_memcpy(log->buf + log->buf_free, str, len);
 str_len -= len;
 str = str + len;
 . . .
 if(data_len = OS_FILE_LOG_BLOCK_SIZE - LOG_BLOCK_TRL_SIZE){ /*完成一个block的写入*/
. . .
      len += LOG_BLOCK_HDR_SIZE + LOG_BLOCK_TRL_SIZE;
      log->lsn = ut_dulint_add(log->lsn, len);
 . . .
 }
 else /*更改lsn*/
  log->lsn = ut_dulint_add(log->lsn, len);
 . . .
}

?

LSN是不会减小的,它是日志位置的唯一标记。在重做日志写入、checkpoint构建和PAGE头里面都有LSN。

关于日志写入:

例如当前重做日志的LSN = 2048,这时候innodb调用log_write_low写入一个长度为700的日志,2048刚好是4个block长度,那么需要存储700长度的日志,需要量个BLOCK(单个block只能存496个字节)。那么很容易得出新的LSN = 2048 + 700 + 2 * LOG_BLOCK_HDR_SIZE(12) + LOG_BLOCK_TRL_SIZE(4) = 2776。

关于checkpoint和日志恢复:

在page的fil_header中的LSN是表示最后刷新是的LSN, 假如数据库中存在PAGE1 LSN = 1024,PAGE2 LSN = 2048, 系统重启时,检测到最后的checkpoint LSN = 1024,那么系统在检测到PAGE1不会对PAGE1进行恢复重做,当系统检测到PAGE2的时候,会将PAGE2进行重做。一次类推,小于checkpoint LSN的页不用重做,大于LSN checkpoint的PAGE就要进行重做。

2.Log Block

innodb在日志系统里面定义了log block的概念,其实log block就是一个512字节的数据块,这个数据块包括块头、日志信息和块的checksum.其结构如下:

\

Block no的最高位是描述block是否flush磁盘的标识位.通过lsn可以blockno,具体的计算过程是lsn是多少个512的整数倍,也就是no = lsn / 512 + 1;为什么要加1呢,因为所处no的块算成clac_lsn一定会小于传入的lsn.所以要+1。其实就是block的数组索引值。checksum是通过从块头开始到块的末尾前4个字节为止,做了一次数字叠加,代码如下:

sum = 1;
 sh = 0;
 for(i = 0; i < OS_FILE_LOG_BLOCK_SIZE - LOG_BLOCK_TRL_SIZE, i ++){
      sum = sum & 0x7FFFFFFF;
      sum += (((ulint)(*(block + i))) << sh) + (ulint)(*(block + i));
      sh ++;
      if(sh > 24) 
        sh = 0;
 }
在日志恢复的时候,innodb会对加载的block进行checksum校验,以免在恢复过程中数据产生错误。事务的日志写入是基于块的,如果事务的日志大小小于496字节,那么会合其他的事务日志合并在一个块中,如果事务日志的大小大于496字节,那么会以496为长度进行分离存储。例如:T1 = 700字节大小,T2 = 100字节大小存储结构如下:

?

\

?

3.重做日志结构和关系图

innodb在重做日志实现当中,设计了3个层模块,即redo log buffer、group files和archive files。这三个层模块的描述如下:

?

?

redo log buffer 重做日志的日志内存缓冲区,新写入的日志都是先写入到这个地方.redo log buffer中数据同步到磁盘上,必须进行刷盘操作。

group files 重做日志文件组,一般由3个同样大小的文件组成。3个文件的写入是依次循环的,每个日志文件写满后,即写下一个,日志文件如果都写满时,会覆盖第一次重新写。重做日志组在innodb的设计上支持多个。

archive files 归档日志文件,是对重做日志文件做增量备份,它是不会覆盖以前的日志信息。

以下是它们关系示意图:
\

?

3.1重做日志组

重做日志组可以支持多个,这样做的目的应该是为了防止一个日志组损坏后,可以从其他并行的日志组里面进行数据恢复。在MySQL-5.6的将日志组的个数设置为1,不允许多个group存在。网易姜承尧的解释是innodb的作者认为通过外层存储硬件来保证日志组的完整性比较好,例如raid磁盘。重做日志组的主要功能是实现对组内文件的写入管理、组内的checkpoint建立和checkpiont信息的保存、归档日志状态管理(只有第一个group才做archive操作).以下是对日志组的定义:

?

typedef struct log_group_struct
{
 ulint id;                             /*log group id*/
 ulint n_files;                     	/*group包含的日志文件个数*/
 ulint file_size;                  	/*日志文件大小,包括文件头*/
 ulint space_id;                 	/*group对应的fil_space的id*/
 ulint state;                        /*log group状态,LOG_GROUP_OK、LOG_GROUP_CORRUPTED*/
 dulint lsn;                         /*log group的lsn*/
 dulint lsn_offset;             	/*当前lsn相对组内文件起始位置的偏移量 */
 ulint n_pending_writes; 		/*本group 正在执行fil_flush的个数*/
 byte** file_header_bufs; 	/*文件头缓冲区*/
 byte** archive_file_header_bufs;/*归档文件头信息的缓冲区*/
 ulint archive_space_id;     	/*归档重做日志ID*/
 ulint archived_file_no;     	/*归档的日志文件编号*/
 ulint archived_offset;     	/*已经完成归档的偏移量*/
 u