版本
mysql-8.0.22 社区版
源码
结合源码来介绍redo日志的各个模块。
redo日志的写入
写入系统缓存
log buffer是循环使用,需要将log buffer中的内容,持续的写入到系统缓存中。有较多的地方会将log buffer中的内容写入系统缓存,比如commit、比如后台线程持续的写入等等。
相关变量定义
log_t *log_sys
| write_lsn // 当前写入系统缓存的最大的LSN
| current_file_lsn // 通过LSN计算对应物理文件的位置
| current_file_real_offset
| current_file_end_offset // 当前文件的最大OFFSET
| recent_written // 用户判断当前可以写入的最大LSN
写入
判断可以写入的最大LSN
在将log buffer写入到系统缓存时,我们需要写入连续的内存,不能留有空洞,这样才能保证write_lsn之前的log buffer是可以复用的。redo日志是并发写入的,这里引入recent_written来保证连续的redo日志写入。
recent_written的定义如下:
Link_buf<lsn_t> recent_written
| m_capacity; // 该Link_buf最大的lsn长度
| m_links
| m_tail // 当前可以write的最大lsn
流程如下:
- 每次写入redo日志到log buffer时,会将该日志的start_lsn 和 end_lsn记录到recent_written中,使用m_links[start_lsn] = end_lsn来记录。LSN是连续的,那么end_lsn为下一次redo日志的起始位置。
- 当查找时,如果m_links[curr_lsn]不为0,则表示这一段lsn还没有写入到log buffer中。当前允许写入到系统缓存的最大lsn就是curr_lsn。
源码如下:
Link_buf<Position>::add_link (Position from, Position to)
| index = from & (m_capacity - 1)
| m_links[index].store(to)
Link_buf<Position>::advance_tail
| from = m_tail.load()
| next = m_links[from]
// mlinks 没有被更新 (0 或者 遗留的旧值) 则表示还没有写入
// 如果有值则继续推进,一直到第一个没有写入
| if (next < from) return
else m_tail.store(from )
举例说明:
三条redo日志的lsn为(10,15)、(15,20)、(20,25)。当第一条和第三条写入完成时,recent_written中的记录为:
recent_written.m_tail = 10
m_links[10] = 15
m_links[15] = 0
m_links[20] = 25
// 调用该函数后,m_tail更新为15,表示LSN为15之前的log buffer可以写入系统缓存
Link_buf<Position>::advance_tail()
后台线程 log_writter 写入流程
后台线程,log buffer写入系统缓存的流程如下:
- 获取本次写入能写到的最大LSN
- 通过输入的LSN计算本次写入的起始位置和结束位置
- 自适应调整结束位置:不跨文件边界,使用的redo日志空间可以被覆盖
- 将LSN转换为redo日志文件的位置,并进行插入
- 更新write_lsn等元数据
log buffer写入系统缓存的接口如下
1. 通过recent_written获得当能能写入的最大的LSN
ready_lsn = log_buffer_ready_for_write_lsn(log);
| return log.recent_written.tail()
log_writer_write_buffer(log, ready_lsn)
| 2. 计算起始和结束位置start_offset、 end_offset
| size_t start_offset = write_lsn % log.buf_size;
| size_t end_offset = ready_lsn % log.buf_size;
| 3. 自适应的调整写入的最大LSN
| if (start_offset >= end_offset) end_offset = log.buf_size;
| if (checkpoint_limited_lsn < ready_lsn) ready_lsn = checkpoint_limited_lsn;
| 4. 将lsn转换为物理文件的位置并进行插入
| log_files_write_buffer( log, buf_begin, buf_end - buf_begin, ...
| | real_offset = compute_real_offset(log, start_lsn)
| | ...
| | write_blocks(log, write_buf, write_size, real_offset);
| |
| | 5. 更新write_lsn、buf_limit_sn(log buffer写入上限)等原数据
| | log.write_lsn.store(new_write_lsn);
| | log_update_buf_limit(log, new_write_lsn);
commit 写入流程
这里包括autocommit、ddl 等操作,不局限于某一种操作。写入流程如下:
- 等待可写入lsn 超过输入的参数 lsn
- 循环写入,直到write_lsn 到达或者超过 lsn
写入接口如下:
// 参数flush决定log buffer的redo写入系统缓存后,是否刷新内容到磁盘,这里暂时不做讨论。
log_write_up_to(*log_sys, lsn, flush) <==> log_self_write_up_to
1. 等待ready_lsn 超过输入的lsn
| while (i < srv_n_spin_wait_rounds && ready_lsn < end_lsn) {
| log.recent_written.advance_tail();
ready_lsn = log_buffer_ready_for_write_lsn(log);
2. 等待写入的lsn到ready_lsn
| while (write_lsn < ready_lsn)
| // 这里的写入同后台线程相同
| log_writer_write_buffer(log, log_buffer_ready_for_write_lsn(log));