Innodb redo log分析

目录

概述... 2

Redo log配置参数... 2

Redo log存储结构... 3

Redo log记录格式... 5

相关lsn说明... 6

redo log文件初始化... 6

redo log运行流程... 7

Mini transaction. 7

redo线程... 8

用户线程... 8

log_writer 8

log_flusher 8

log_closer 9

log_checkpointer 9

log_write_notifier 10

log_flush_notifier 10

Link_buf数据结构... 10

 

 

 

Mysql版本:8.0.15

概述

       用于记录物理文件数据的修改,用于崩溃恢复,保证事务持久化。

Redo log配置参数

innodb_log_group_home_dir:该参数用来指定redo日志存放的路径

innodb_log_files_in_group:redo log文件的个数,最少2个,最多100个,默认2个(代码中配置),以ib_logfile[num]命名

Innodb_log_file_size:redo log文件的大小

所以,总的redo Log的大小为 innodb_log_files_in_group * innodb_log_file_size

innodb_log_buffer_size:该参数控制日志内存大小

innodb_flush_log_at_trx_commit:控制事务提交时日志是否刷盘。

当设置该值为1时,每次事务提交都要做一次fsync,这是最安全的配置,即使宕机也不会丢失事务;

当设置为2时,则在事务提交时只做write操作,只保证写到系统的page cache,因此实例crash不会丢失事务,但宕机则可能丢失事务;

当设置为0时,事务提交不会触发redo写操作,而是留给后台线程每秒一次的刷盘操作,因此实例crash将最多丢失1秒钟内的事务。

innodb_log_write_ahead_size :预写日志大小

innodb_redo_log_encrypt:日志是否加密

 

 

参数

说明

innodb_flush_log_at_timeout

超时是否刷日志

innodb_log_buffer_size

日志内存的大小

innodb_log_checkpoint_fuzzy_now

 

innodb_log_checkpoint_now

是否现在做checkpoint

innodb_log_checksums

日志校验和规则,默认crc

innodb_log_compressed_pages

日志记录是否使用压缩页中记录

innodb_log_spin_cpu_abs_lwm

 

innodb_log_spin_cpu_pct_hwm

 

innodb_log_wait_for_flush_spin_hwm

 

innodb_log_write_ahead_size

预写redo大小,默认8k

innodb_online_alter_log_max_size

在线改变日志文件的大小

innodb_redo_log_encrypt

日志是否加密

 

Redo log存储结构

尽管Redo Log有多个文件,但每个文件的组成结构是一样的,只是有一些数据只会存在的第一个Log文件(ib_logfile0)的文件头中, 例如Buffer Pool flush checkpoint信息只会写在第一个log文件的文件头中。

 

ib_logfile0 结构说明

0~511

Log file Header block

512~1011

Checkpoint block

1024~1535

Unused(512字节)

1536~2047

Checkpoint block

2048~xxx

Log blocks(每一个512字节)

其他日志文件结构说明

 

ib_logfile1~n 结构说明

0~511

Log file Header block

512~1023

Unused(512字节)

1024~1535

Unused(512字节)

1536~2047

Unused(512字节)

2048~xxx

Log blocks(每一个512字节)

 

Redo log 中有3中不同的block:Log file header block、Checkpoint block和Log file block分别对每一种block的结构做详细说明。

Log file header block

 

Log file header block 结构说明

0~3

Log Header Format(4字节)

4~7

PAD(4字节)

8~15

Start LSN (8字节)

16~47

Creator (32字节)

48~507

Left bytes(460字节)

508~511

Checksum(4字节)

Log Header Format:用来标识当前日志文件的格式版本。例如如果为0则表示这个redo log是有5.7.9以前的MySQL生成的。

PAD:没有任何含义,目前仅仅用来做一些对齐处理。

Start LSN:这个字段用在CloneArchive场景,与一般的持久性、崩溃恢复无关,这里不做讨论。

Creator:存储的是创建这个log文件创建者的名称的字符串。

Left Bytes:目前没有任何含义,仅仅是用来填充占位,以便让这个block达到512字节大小。

Checksum:检验和

Checkpoint block

 

Checkpoint block 结构说明

0~7

Checkpoint number(8字节)

8~15

Checkpoint lsn(8字节)

16~23

Checkpoint offset(8字节)

24~31

Buf size(8字节)

32~507

Left bytes(476字节)

508~512

Checksum(4字节)

checkpoint number可以理解为checkpoint域写盘的次数,每次写盘递增1,同时这个值取模2可以实现2个checkpoint域轮流写。

checkpoint LSN:表示小于这个Checkpoint LSN的日志记录都已经写入到了实际的数据文件中,Crash Recovery系统从Checkpoint LSN之后的第一个MTR记录开始进行数据恢复。

checkpoint offset:Checkpoint LSN对应在Log files中的文件偏移量,这个用来对LSN和Offset之间转换进行校准。

Buf size:MySQL系统内只对该字段执行了写入,并未进行读取然后进行相应的处理。它标识的是系统当前Log buffer的大小。

Left bytes:目前没有任何含义,仅仅是用来填充占位,以便让这个block达到512字节大小。但在这里最后4个字节用来存放该checkpoint域的Checksum。

Checksum:检验字节

Log file block

包括header(12字节)、records(496字节)和tailer(4字节)

 

Log file block 结构说明

0~3

Log block number(4字节)

4~5

Data length(2字节)

6~7

First Record offset (2字节)

8~11

Log Block Checkpoint number (4字节)

12~508

Log Records (496字节)

508~512

Checksum(4字节)

Log Block Number:Log Block的编号,从1开始递增,达到最大值(0x3FFFFFFF+1)后再继续从1开始。

Data length:写入到当前block的字节数,如果存日志大小都是512

Firsrt Record offset:该Block内第一个mtr记录的起始偏移量 ,

值为0表示当前块的日志都属于同一个mtr,即和上一个block属于同一个mtr;

值为12表示上一个日志刚好写满一个block;

值大于12小于496表示上一个日志跨了两个block,,且当前block没写满

Log Block Checkpoint number:该block所处在的checkpoint no

Log Records:一个block内可以存储多条mtr记录,同样一个mtr记录可以跨越多个block.

Checksum:检验字节

 

 

Redo log记录格式

InnoDB redo log的格式可以概括为:  <操作类型>+<Space ID>+<Page NO> +<数据>.

Redo Log的日志类型

   Redo Log记录的页面操作大致可以分为以下几种类型:

  1. 在页面上写入N个字节的内容,这些可以看作是物理的Log.

     MLOG_1BYTE

     MLOG_2BYTES,

     MLOG_4BYTES,

     MLOG_8BYTES,

     MLOG_WRITE_STRING

     各种Page链表的指针修改,以及文件头,段页等的内容的修改都是以这种方式记录的日志。

  B. 页面上的记录操作。

     MLOG_REC_*, 

     MLOG_LIST_*,

     MLOG_COMP_REC_*,

     MLOG_COMP_LIST_*

     这些日志记录了对B-Tree页的INSER, DELETE, UPDATE操作和分裂合并操作。
  C. 文件和Page操作

     MLOG_FILE_CREATE, 

     MLOG_FILE_RENAME, 

     MLOG_FILE_DELETE,

     MLOG_PAGE_CREATE, 

     MLOG_PAGE_REORGANIZE

     MLOG_INIT_FILE_PAGE,

  D. Undo Log操作

     MLOG_UNDO_*

     InnoDB中将undo log的操作也记入了redo log. 为什么要这样做,在前面‘恢复’已经说了. 

   这里只提到了部分Redo Log的类型,完整的定义在mtr0types.h文件中。

 

相关lsn说明

sn:当前产生的日志的数量,不包含日志头和尾

write_lsn:该lsn之前的日志已经从log buffer刷新到page cache中

flushed_to_disk_lsn:该lsn之前日志已经刷新到磁盘上

last_checkpoint_lsn:上一次checkpoint的lsn,该lsn之前的脏页已经刷盘

available_for_checkpoint_lsn:可以进行checkpoint的lsn,也就是说脏页已经刷到该lsn,如果有没有落盘的脏页,在checkpoint时候,把脏页同步落盘

log_buffer_ready_for_write_lsn:log.recent_written.tail()返回log buffer中没有空洞连续日志的最后的lsn,可以把日志刷盘到该lsn

log_buffer_dirty_pages_added_up_to_lsn:log.recent_closed.tail():代表该lsn对应的脏页已经挂在flush list上,并且lsn是有序的

sn和lsn相互转换

mtr产生mlog是用sn表示的,为了管理这些mlog,在mlog的头尾加上一些描述信息形成redo log,用lsn表示。sn是mlog的***,lsn是redo log的***。

相同点:都是递增的。

不同点:lsn比sn多了12字节文件头和4字节的文件尾

规定一个redo日志block是512(OS_FILE_LOG_BLOCK_SIZE)字节,包括496(LOG_BLOCK_DATA_SIZE)字节的日志内容、12(LOG_BLOCK_HDR_SIZE)字节文件头描述信息和4(LOG_BLOCK_TRL_SIZE)字节文件尾的校验信息。具体存储格式参考前面的说明。

 

sn 对应buf_size_sn,是由buffer size转换而来,在预留空间时候用到

lsn 对应内存中log buffer的buffer size,mlog加上文件头尾复制到log buffer

 

sn 到lsn

lsn = sn / LOG_BLOCK_DATA_SIZE * OS_FILE_LOG_BLOCK_SIZE +

          sn % LOG_BLOCK_DATA_SIZE + LOG_BLOCK_HDR_SIZE

lsn到sn

sn = lsn / OS_FILE_LOG_BLOCK_SIZE * LOG_BLOCK_DATA_SIZE;

diff = lsn % OS_FILE_LOG_BLOCK_SIZE;

  if (diff < LOG_BLOCK_HDR_SIZE) {

    return (sn);

  }

  if (diff > OS_FILE_LOG_BLOCK_SIZE - LOG_BLOCK_TRL_SIZE) {

    return (sn + LOG_BLOCK_DATA_SIZE);

  }

  return (sn + diff - LOG_BLOCK_HDR_SIZE);

Mini transaction

Mini transaction(简称mtr)是InnoDB对物理数据文件操作的最小事务单元,用于管理对Page加锁、修改、释放、以及日志提交到公共buffer等工作。一个mtr操作必须是原子的,一个事务可以包含多个mtr。每个mtr完成后需要将本地产生的日志拷贝到公共缓冲区,将修改的脏页放到flush list上。

 

一条完整的mlog结构:

log_type(1字节)+space_id(变字节)+page_no(变字节)+页修改数据

  1. 如果mlog中只有一条记录(single record),则将log_type的最高位置1
  2. 如果mlog中含有多条记录(multi record),则在mlog的结尾加一个multi_end类型结束标志
  1. Mini transaction(mtr)产生mlog,保存在mtr的mtr::m_impl::m_log中,mtr提交的时候,将mlog拷贝到log buffer中,在buffer中按lsn有小到大连续存放

Type

Space_id

Page_no

data

type

Space_id

Page_no

data

 

 

 

  1. 多个mtr可以并发产生mlog,并发拷贝到log buffer,是通过link_buffer实现并发写入

 

 

物理事务的流程

  1. 首先开始物理事务,调用mtr.start()函数,初始化事务的变量,默认模式为MTR_LOG_ALL,记录日志并且将脏页加到flush_list。
  2. 物理事务操作
    1. mlog.open():获得保存log的动态buffer和保存page与锁类型的动态buffer
    2. 获取将要修改的数据页,调用mtr_memo_push()函数将页指针和锁类型保存在m_memo中
    3. 向mlog中写入数据页的修改信息
    4. mlog.close():在动态buffer中记录log的长度
  3. 物理事务提交,调用mtr.commit()函数
  1. 将mlog写到log buffert中
    1. 如果mlog是单条记录,则将log_type的最高位设置为MLOG_SINGLE_REC_FLAG
    2. 如果是mlog中有多条记录,则在mlog的尾部追加MLOG_MULTI_REC_END
    3. 对log buffer加读锁,为mlog的写入预留空间,大小为该mlog的长度。计算mlog起始和终止的lsn,由log.sn转换而来
      1. 如果mlog长度大于buf_size_sn,则需要扩展log buffer的size;buf_size_sn是由srv_log_buffer_size转换而来的。
      2. 如果log buffer空间不够,则需要将log buffer中的日志刷盘,调用log_write_up_to()函数,只写到page cache不同步到磁盘,保证log buffer中至少有一个block的空间
    4. 将mlog加上文件头尾写到log buffer中,按512字节一个block存放,如果mlog的长度跨多个block,需在文件头设置first_rec_greoup_offset。log buffer中存的是lsn日志,也就是mlog加上文件头和文件尾,但是block描述信息没有写入,在日志刷盘的时候写入。
    5. 更新link_buffer将mlog对应的start_lsn和长度写入变量recent_written,如果recent_written没有空间,则等待日志刷盘直到有空间
    6.  
  2. 将脏页添加到flush list上,分两种情况:1)生成log,2)不生成log
      1. 生成log
        1. 如果该脏页已经在flush list上,则不需要添加
        2. 更新link_buffer将mlog对应的start_lsn和长度写入变量recent_closed,如果recent_closed没有空间,则等待脏页刷盘直到有空间
      2. 不生成log
        1. 脏页的start_lsn和end_lsn都是0,把该页添加到flush_list需要borrow一个lsn

 

 

redo log文件初始化

实例重启时,会扫描data目录下日志文件的状态

  1. 如果日志文件个数小于2个则报错,至少2个日志文件,并且每个日志文件的大小不一致也报错
  2. 扫描完所有日志文件后,初始化log_sys,启动日志相关线程进行崩溃恢复(后面详细说明崩溃恢复)
  3. 基于redo log的恢复完成后,如果配置的日志文件大小和日志文件个数跟data目录下日志文件不一致,则需要删除旧的日志文件(unlink),重新创建日志文件(create_log_files)、初始化log_sys结构变量(log_sys_init、log_start)、启动日志相关线程(log_start_background_threads)

log_sys初始化(log_sys_init)时,current_file_lsn从8192开始递增,对应在日志文件中真正的offset(current_file_end_offset)是2k(即文件头的长度,也就是4个日志块),使用log_files_update_offsets()函数换算lsn的偏移量。

 

代码实现在srv_start()函数中。

 

redo log运行流程

                                       innodb redo log分析

                                                                      日志流转图

  1. 用户线程的mtr产生本地mlog,mtr提交时,将mlog拷贝到log buffer中,更新link_buffer中的log.recent_wtitten;同时把修改的脏页挂到flush list上,更新link_buffer中的log.recent_closed,这个步骤是并发执行。从这可以发现flush list上的page是无序的。
  2. log writer线程从log buffer中将日志写到FS cache中,更新log.write_lsn,并通知log flusher线程
  3. log flusher线程负责把日志同步到磁盘,更新log. flushed_to_disk_lsn,如果srv_flush_log_at_trx_commit=1则去通知log_flush_notifer线程唤醒等待用户线程

 

 

redo线程

用户线程

并发写入log buffer,如果写之前发现log buffer剩余空间不足,调用log_write_up_to函数,唤醒等在writer_event上的log_writer线程来将log buffer数据写入page cache释放log buffer空间,在此期间,用户线程会等待在write_events[]上,等待log_writer线程写完page cache后唤醒,用户线程被唤醒后,代表当前log buffer有空间写入mtr对应的redo log,将其拷贝到log buffer对应位置,然后在recent_written上更新对应区间标记,接着将对应脏页挂到flush list上,并且在recent_closed上更新对应区间标记。

log_writer

实现原理

Log_writer后台异步线程,在writer_event上等待用户线程唤醒os_event_set(log.writer_event)或者timeout(默认10us),唤醒后扫描recent_written,检测从write_lsn后,log buffer中是否有新的连续log,有的话就将他们一并写入page cache,然后唤醒此时可能等待在write_events[]上的用户线程或者等待在write_notifier_event上的log_write_notifier线程,接着唤醒等待在flusher_event上的log_flusher线程。

 

Log writer每次写入的日志都是512字节对齐写入,大小最大为srv_log_write_ahead_size。并且src_write_ahead_buffer循环使用。

代码详细说明

线程函数:

     void log_writer(log_t *log_ptr)

Step1.初始化线程等待结构Log_thread_waiting,log_writer线程等待用户线程的唤醒或者超时。

log.writer_event :writer线程事件

srv_log_writer_spin_delay:自旋迭代次数

srv_log_writer_timeout:超时时间

Log_thread_waiting waiting{log, log.writer_event, srv_log_writer_spin_delay, srv_log_writer_timeout}

Step2.死循环进入等待状态,stop_condition是匿名函数,调用函数log_advance_ready_for_write_lsn不断向前推进recent_written.tail(),保证lsn连续。当有用户线程执行os_event_set(log.writer_event)或者超时,则线程被唤醒,开始写日志

for (uint64_t step = 0;; ++step) {

……    

 const auto wait_stats = waiting.wait(stop_condition);

……

}

step3.被唤醒后,执行写入操作,

log_writer_write_buffer(log, ready_lsn)

 

  1. 每次最多写入日志的长度为从write lsn到log buffer的末尾,如果连续的日志长度太长,分多次写入,保证写入日志相对log buffer位置递增
  2. 写入日志前需保证日志文件有足够空间
  3. 计算上一次write lsn在磁盘上的偏移,
    1. 如果当前日志文件一个字节的空间也没有,则开启写到下一个文件,从新写入
    2. 如果当前日志文件剩余空间小于本次写入日志长度,则写到文件末尾
  4. 启用write ahead机制写入,避免read-modify-write,当写入日志的偏移在write ahead buffer的结束位置,说明在内存中没有该次写入的page cache,需要write ahead操作;当执行完write ahead操作后,在内存中有该区域对应的page cache,后续对当前write ahead buffer可以覆盖的文件区域的写日志,都可以命中这些page cache, 可以直接写入。
    1. write ahead buffer没有空间,申请新的write ahead buffer(8k)
      1. 如果写入日志的长度大于8k,则将写入日志长度置为8k,返回写入日志长度为8k。可直接将日志从log buffer写到page cache
      2. 如果写入日志的长度小于8k,返回原来写入日志长度。需将日志拷贝到write ahead buffer,执行write ahead逻辑prepare_for_write_ahead函数,将剩余空间置0。从write ahead buffer写入page cache,写入长度为write ahead buffer size。
    2. write ahead buffer空间小于写入日志的长度,将写入日志的长度设置为write ahead buffer剩余空间的长度,该长度肯定是512的倍数,因为write ahead buffer都是按512对齐写入的。将日志从log buffer写到page cache,这部分区域已经被上一次write ahead buffer预写过了
    3. write ahead buffer空间足够
      1. 如果写入长度小于512字节,则将日志拷贝到write ahead buffer,从write  ahead buffer写入page cache,如果write ahead buffer没有写满,则将后面的空间置0,写入512字节,但是write_lsn推进实际的长度。
      2. 如果写入长度大于512字节,则将写入长度对512向下对齐。比如写入的日志长度为1050,对齐后是1024,只写入1024长度
  5. 如果srv_flush_log_at_trx_commit等于1,os_event_set(log.flusher_event)唤醒log flusher线程,将日志同步磁盘
  6.  

step4.写入完成后,如果没有要写入的日志,继续进入等待状态。如果要停止log_writer线程,要保证buffer中的日志全部写入到OS cache中,否则继续循环写入

if (log.should_stop_threads.load()) {

        if (!log_advance_ready_for_write_lsn(log)) {

          break;

        }

        ready_lsn = log_buffer_ready_for_write_lsn(log);

}

 

 

 

log_flusher

实现原理

在flusher_event上等待log_writer线程或者其他用户线程(调用log_write_up_to true)唤醒,比较上次刷盘的flushed_to_disk_lsn和当前写入page cache的write_lsn,如果小于后者,就将增量刷盘,并推进flushed_to_disk_lsn(原子数据),然后唤醒可能等待在flush_events[]上的用户线程(调用log_write_up_to true)或者等待在flush_notifier_event上的log_flush_notifier

 

触发条件

  1. innodb_flush_log_at_trx_commit参数设置为1时;如果不等于1,每隔1s刷一次
  2. 快速刷脏页时(用户线程调用log_write_up_to函数触发)
  3. log_writer线程停止时

 

 

 

代码详细说明

 

 

log_closer

实现原理

不等待任何条件变量,每隔一段时间,会扫描recent_closed,向前推进,recent_closed.m_tail代表之前的lsn已经都挂在flush_list上了,用来取checkpoint时用。

 

代码详细说明

 

 

log_checkpointer

意义

减少崩溃恢复的时间

实现原理

checkpoint即检查点之前的日志对应的脏页已经落盘,日志可以被覆盖。确定检查点的日志***(checkpoint lsn)。

  1. 保证checkpoint lsn小于等于日志刷盘的lsn(flushed_to_disk_lsn)
  2. Checkpoint lsn大于等于last checkpoint lsn
  3. 获取落盘脏页的lsn
    1. recent_closed.m_tail,它代表在此之前的lsn对应的脏页都已经挂在了flush_list上,并且lsn是连续的,得到是连续脏页的最大的lsn(dirty_page_lsn)
    2. flush_list上取oldest_modification最小的lsn,flush list上有2M的并发写入,flush list上的page已经是无序的。获取到的只是近似最小的oldest_modification_lsn,在减去link buffer容量2M得到low_water_lsn,即可保证low_water_lsn之前的脏页已经落盘。这里说明一下,link buffer容量2M,最多可以存2M长度的lsn,超过这个长度将触发脏页刷盘操作

取(low_water_lsn,dirty_page_lsn,flushed_to_disk_lsn)中的最小值,得到available_for_checkpoint_lsn

 

 

 

 

触发条件

  1. 日志空间不够
  2. 每隔10ms自动触发
  3. Log_closer线程停止

代码详细说明

写checkpoint lsn,在ib_logfile0文件的第一页,字节偏移1一个block或者3个block的位置写入512字节。

 

log_write_notifier

实现原理

等待log_writer线程唤醒,扫描并唤醒上一次log.write_lsn和当前log.write_lsn区间覆盖的所有slot上的用户线程。

代码详细说明

线程函数:

void log_flush_notifier(log_t *log_ptr)

Step1.初始化线程等待结构Log_thread_waiting,log_write_notifier线程等待log_writer线程的唤醒或者超时。

log. write_notifier_eventwrite_notifier线程事件

srv_log_write_notifier_spin_delay:自旋迭代次数

srv_log_write_notifier_timeout:超时时间

Log_thread_waiting waiting{log, log.write_notifier_event, srv_log_write_notifier_spin_delay,  srv_log_write_notifier_timeout};

Step2.死循环进入等待状态,stop_condition是匿名函数。当有用户线程执行os_event_set(log.write_notifier_event)或者超时,则线程被唤醒,开始写日志

for (uint64_t step = 0;; ++step) {

……    

 const auto wait_stats = waiting.wait(stop_condition);

……

}

step3.被唤醒后,执行唤醒用户线程

    while (lsn <= notified_up_to_lsn) {

      const auto slot =

          (lsn - 1) / OS_FILE_LOG_BLOCK_SIZE & (log.write_events_size - 1);

      lsn += OS_FILE_LOG_BLOCK_SIZE;

      os_event_set(log.write_events[slot]);

    }

step4.唤醒用户线程完成后,继续进入等待状态。如果要停止log_write_notifier线程,要保证当前的lsn大于log.write_lsn,否则继续循环

   if (!log.writer_thread_alive.load()) {

      if (lsn > log.write_lsn.load()) {

        ut_a(lsn == log.write_lsn.load() + 1);

        break;

      }

    }

 

log_flush_notifier

实现原理

实现原理和log_write_notifier一样。等待log_flusher线程唤醒,扫描并唤醒上一次log.flushed_to_disk_lsn和当前log.flushed_to_disk_lsn区间覆盖的所有slot上的用户线程。

代码详细说明

线程函数:

void log_flush_notifier(log_t *log_ptr)

Step1.初始化线程等待结构Log_thread_waiting,log_flush_notifier线程等待log_flusher线程的唤醒或者超时。

log. flush_notifier_eventflush_notifier线程事件

srv_log_flush_notifier_spin_delay:自旋迭代次数

srv_log_flush_notifier_timeout:超时时间

Log_thread_waiting waiting{log, log.flush_notifier_event, srv_log_flush_notifier_spin_delay,  srv_log_flush_notifier_timeout};

Step2.死循环进入等待状态,stop_condition是匿名函数,调用函数log_advance_ready_for_write_lsn不断向前推进recent_written.tail(),保证lsn连续。当有用户线程执行os_event_set(log.writer_event)或者超时,则线程被唤醒,开始写日志

for (uint64_t step = 0;; ++step) {

……    

 const auto wait_stats = waiting.wait(stop_condition);

……

}

step3.被唤醒后,执行唤醒用户线程

    while (lsn <= notified_up_to_lsn) {

      const auto slot =

          (lsn - 1) / OS_FILE_LOG_BLOCK_SIZE & (log.flush_events_size - 1);

      lsn += OS_FILE_LOG_BLOCK_SIZE;

      os_event_set(log.flush_events[slot]);

    }

step4.唤醒用户线程完成后,继续进入等待状态。如果要停止log_flush_notifier线程,要保证当前的lsn大于log.flushed_to_disk_lsn,否则继续循环

    if (!log.flusher_thread_alive.load()) {

      if (lsn > log.flushed_to_disk_lsn.load()) {

        ut_a(lsn == log.flushed_to_disk_lsn.load() + 1);

        break;

      }

    }

 

Link_buf数据结构

就是一个固定大小的数组,代码中recent_written和recent_closed用到link_buf<uint64>结构,recent_written大小(m_capacity)默认为1024*1024,recent_closed大小(m_capacity)默认为2*1024*1024,lsn对数组长度求余得到在数组中的index,即数组下标,数组中存的是end_lsn - start_lsn的长度。

m_tail中存的是lsn表示该lsn之前的日志都是连续的。

advance_tail_until()函数向前推进数组,如果数组中的长度为0,则停止,同时将m_tail前面的数组中的长度置0,即回收数组,更新m_tail的值。

用在mlog并发写入log_buffer时

例如:以简单的recent_written为例,m_capacity=8,初始化时数组长度都为0,m_tail=0,数组下标=lsn%8得到的,下一个数组对应的lsn=当前lsn+对应数组中存的长度

长度

12

0

56

0

67

0

0

0

数组下标

0

1

2

3

4

5

6

7

假设最后写入lsn对应的slot为2,调用advance_tail_until()函数之后的数组为

长度

0

0

0

0

0

0

0

0

数组下标

0

1

2

3

4

5

6

7

并更新m_tail,m_tail=slot[2]对应的lsn+56

 

Innodb启动时调用log_start()传入的参数就是,flushed_lsn和checkpoint_lsn,保证了lsn的连续,崩溃恢复后,flused_lsn等于崩溃恢复后的lsn,也会调用log_start函数,从flushed_lsn开始新的log。

 

 

待后续完善。。。

 

 

 

相关文章: