Innodb redo log分析
目录
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:这个字段用在Clone和Archive场景,与一般的持久性、崩溃恢复无关,这里不做讨论。
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记录的页面操作大致可以分为以下几种类型:
- 在页面上写入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(变字节)+页修改数据
- 如果mlog中只有一条记录(single record),则将log_type的最高位置1
- 如果mlog中含有多条记录(multi record),则在mlog的结尾加一个multi_end类型结束标志
- 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 |
|
|
- 多个mtr可以并发产生mlog,并发拷贝到log buffer,是通过link_buffer实现并发写入
物理事务的流程
- 首先开始物理事务,调用mtr.start()函数,初始化事务的变量,默认模式为MTR_LOG_ALL,记录日志并且将脏页加到flush_list。
- 物理事务操作
- mlog.open():获得保存log的动态buffer和保存page与锁类型的动态buffer
- 获取将要修改的数据页,调用mtr_memo_push()函数将页指针和锁类型保存在m_memo中
- 向mlog中写入数据页的修改信息
- mlog.close():在动态buffer中记录log的长度
- 物理事务提交,调用mtr.commit()函数
- 将mlog写到log buffert中
- 如果mlog是单条记录,则将log_type的最高位设置为MLOG_SINGLE_REC_FLAG
- 如果是mlog中有多条记录,则在mlog的尾部追加MLOG_MULTI_REC_END
- 对log buffer加读锁,为mlog的写入预留空间,大小为该mlog的长度。计算mlog起始和终止的lsn,由log.sn转换而来
- 如果mlog长度大于buf_size_sn,则需要扩展log buffer的size;buf_size_sn是由srv_log_buffer_size转换而来的。
- 如果log buffer空间不够,则需要将log buffer中的日志刷盘,调用log_write_up_to()函数,只写到page cache不同步到磁盘,保证log buffer中至少有一个block的空间
- 将mlog加上文件头尾写到log buffer中,按512字节一个block存放,如果mlog的长度跨多个block,需在文件头设置first_rec_greoup_offset。log buffer中存的是lsn日志,也就是mlog加上文件头和文件尾,但是block描述信息没有写入,在日志刷盘的时候写入。
- 更新link_buffer将mlog对应的start_lsn和长度写入变量recent_written,如果recent_written没有空间,则等待日志刷盘直到有空间
- 将脏页添加到flush list上,分两种情况:1)生成log,2)不生成log
-
- 生成log
- 如果该脏页已经在flush list上,则不需要添加
- 更新link_buffer将mlog对应的start_lsn和长度写入变量recent_closed,如果recent_closed没有空间,则等待脏页刷盘直到有空间
- 不生成log
- 脏页的start_lsn和end_lsn都是0,把该页添加到flush_list需要borrow一个lsn
- 生成log
-
redo log文件初始化
实例重启时,会扫描data目录下日志文件的状态
- 如果日志文件个数小于2个则报错,至少2个日志文件,并且每个日志文件的大小不一致也报错
- 扫描完所有日志文件后,初始化log_sys,启动日志相关线程进行崩溃恢复(后面详细说明崩溃恢复)
- 基于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运行流程
日志流转图
- 用户线程的mtr产生本地mlog,mtr提交时,将mlog拷贝到log buffer中,更新link_buffer中的log.recent_wtitten;同时把修改的脏页挂到flush list上,更新link_buffer中的log.recent_closed,这个步骤是并发执行。从这可以发现flush list上的page是无序的。
- log writer线程从log buffer中将日志写到FS cache中,更新log.write_lsn,并通知log flusher线程
- 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)
- 每次最多写入日志的长度为从write lsn到log buffer的末尾,如果连续的日志长度太长,分多次写入,保证写入日志相对log buffer位置递增
- 写入日志前需保证日志文件有足够空间
- 计算上一次write lsn在磁盘上的偏移,
- 如果当前日志文件一个字节的空间也没有,则开启写到下一个文件,从新写入
- 如果当前日志文件剩余空间小于本次写入日志长度,则写到文件末尾
- 启用write ahead机制写入,避免read-modify-write,当写入日志的偏移在write ahead buffer的结束位置,说明在内存中没有该次写入的page cache,需要write ahead操作;当执行完write ahead操作后,在内存中有该区域对应的page cache,后续对当前write ahead buffer可以覆盖的文件区域的写日志,都可以命中这些page cache, 可以直接写入。
- write ahead buffer没有空间,申请新的write ahead buffer(8k)
- 如果写入日志的长度大于8k,则将写入日志长度置为8k,返回写入日志长度为8k。可直接将日志从log buffer写到page cache
- 如果写入日志的长度小于8k,返回原来写入日志长度。需将日志拷贝到write ahead buffer,执行write ahead逻辑prepare_for_write_ahead函数,将剩余空间置0。从write ahead buffer写入page cache,写入长度为write ahead buffer size。
- write ahead buffer空间小于写入日志的长度,将写入日志的长度设置为write ahead buffer剩余空间的长度,该长度肯定是512的倍数,因为write ahead buffer都是按512对齐写入的。将日志从log buffer写到page cache,这部分区域已经被上一次write ahead buffer预写过了
- write ahead buffer空间足够
- 如果写入长度小于512字节,则将日志拷贝到write ahead buffer,从write ahead buffer写入page cache,如果write ahead buffer没有写满,则将后面的空间置0,写入512字节,但是write_lsn推进实际的长度。
- 如果写入长度大于512字节,则将写入长度对512向下对齐。比如写入的日志长度为1050,对齐后是1024,只写入1024长度
- write ahead buffer没有空间,申请新的write ahead buffer(8k)
- 如果srv_flush_log_at_trx_commit等于1,os_event_set(log.flusher_event)唤醒log flusher线程,将日志同步磁盘
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
触发条件
- innodb_flush_log_at_trx_commit参数设置为1时;如果不等于1,每隔1s刷一次
- 快速刷脏页时(用户线程调用log_write_up_to函数触发)
- log_writer线程停止时
代码详细说明
log_closer
实现原理
不等待任何条件变量,每隔一段时间,会扫描recent_closed,向前推进,recent_closed.m_tail代表之前的lsn已经都挂在flush_list上了,用来取checkpoint时用。
代码详细说明
log_checkpointer
意义
减少崩溃恢复的时间
实现原理
checkpoint即检查点之前的日志对应的脏页已经落盘,日志可以被覆盖。确定检查点的日志***(checkpoint lsn)。
- 保证checkpoint lsn小于等于日志刷盘的lsn(flushed_to_disk_lsn)
- Checkpoint lsn大于等于last checkpoint lsn
- 获取落盘脏页的lsn
- recent_closed.m_tail,它代表在此之前的lsn对应的脏页都已经挂在了flush_list上,并且lsn是连续的,得到是连续脏页的最大的lsn(dirty_page_lsn)
- 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
触发条件
- 日志空间不够
- 每隔10ms自动触发
- 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_event:write_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_event :flush_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。
待后续完善。。。