InnoDB 的 Redo Log

2023-3-9|2023-3-16
麦兜
麦兜
type
Post
status
Published
date
Mar 9, 2023
slug
summary
tags
数据库
InnoDB
category
学习思考
password
icon
mini-transaction 是内部更低级的概念,简称mtr,修改Page、新增Page等都需要写入Rodo log,用于Crash之后恢复。在全局有一个log_sys 对象,作为redo log元配置对应的结构是log0sys.hlog_tlog_sys 作为redo log的物理和逻辑(redo buffer)枢纽。
 
LSN(Log Sequeue Number):日志序号,初始值8704 按照实际写入的日志量加上占用的log block headerlog block trailer来计算。
SN(Sequeue Number):log buffer 写入位置。
LSN和SN之间的换算关系:
constexpr inline lsn_t log_translate_sn_to_lsn(lsn_t sn) { return (sn / LOG_BLOCK_DATA_SIZE * OS_FILE_LOG_BLOCK_SIZE + sn % LOG_BLOCK_DATA_SIZE + LOG_BLOCK_HDR_SIZE); }

日志结构

notion image

事务流程

mtr.start() mlog_*() //不同类型的日志 mtr.commit()

mini-transaction start

mini-transaction 结构
struct mtr_t { struct Impl { //事务锁 mtr_buf_t m_memo; //事务的本地日志是一个动态分配的内存空间 mtr_buf_t m_log; bool m_made_dirty; bool m_inside_ibuf; bool m_modifications; bool m_marked_nolog; size_t m_shard_index; uint32_t m_n_log_recs; mtr_state_t m_state; Flush_observer *m_flush_observer; }; }
mtr.start() 方法初始化会把 m_log 初始化 new (&m_impl.m_memo) mtr_buf_t();

mini-transaction 数据插入

操作之前会获取各种锁。
//mtr_t.Impl.m_memo的结构 /** mini-transaction memo stack slot. */ struct mtr_memo_slot_t { void *object; /* 加锁的对象. */ ulint type; /* 持有的锁类型,W or R. */ };

mlog_open

首先标记 buf 数据已被修改 mtr m_impl.m_modifications = true;
获取 mtr_t.Impl.m_log 的指针。有可能会扩容。

mlog_write_initial_log_record_fast

m_log中写入typespace idpage no,并增加m_n_log_recs的数量。

mach_write_compressed

根据数字的具体大小,选择从1到4个字节记录整数,写入m_log

mlog_close

//实际上是间接调用dyn的close() //dyn0buf.h /** Closes the buffer returned by open. @param ptr end of used space */ void close(const byte *ptr) { ut_ad(UT_LIST_GET_LEN(m_list) > 0); block_t *block = back(); m_size -= block->used(); block->close(ptr); m_size += block->used(); //统计整个dyn使用内存 }

mini-transaction commit

 
commit 实际上是间接调用 Command.execute 方法,首先执行prepare_write(); 做写日志的准备,如果只生成一条redo log 会把Flag 设置 MLOG_SINGLE_REC_FLAG ,如果是多条会在尾部添加 MLOG_MULTI_REC_END 标志。MLOG_MULTI_REC_END 用于多条日志原子恢复。prepare_write();这个方法会返回redo log的大小。
 
  1. 如果prepare_write(); 返回的的长度大于0接下来调用log_buffer_reserve() 获取handle
    1. log_buffer_reserve() 首先提前计算插入redo bufferstart_snend_lsn 。(这是从MySQL 8.0开始,设计了一套无锁的写log机制。)
      /* Reserve space in sequence of data bytes: */ const sn_t start_sn = log.sn.fetch_add(len); //原子增加
      算完start_snend_lsn 判断是否比buffer的limit大
      if (unlikely(end_sn > log.buf_limit_sn.load())) { log_wait_for_space_after_reserving(log, handle); }
      等待之前日志写入回收空间
      static void log_wait_for_space_in_log_buf(log_t &log, sn_t end_sn) { lsn_t lsn; Wait_stats wait_stats; //当前已经写入文件的 sn const sn_t write_sn = log_translate_lsn_to_sn(log.write_lsn.load()); log_sync_point("log_wait_for_space_in_buf_middle"); const sn_t buf_size_sn = log.buf_size_sn.load(); if (end_sn + OS_FILE_LOG_BLOCK_SIZE <= write_sn + buf_size_sn) { return; } /* We preserve this counter for backward compatibility with 5.7. */ srv_stats.log_waits.inc(); lsn = log_translate_sn_to_lsn(end_sn + OS_FILE_LOG_BLOCK_SIZE - buf_size_sn); //等待 lsn 之前的 redo log 被写入. wait_stats = log_write_up_to(log, lsn, false); MONITOR_INC_WAIT_STATS(MONITOR_LOG_ON_BUFFER_SPACE_, wait_stats); ut_a(end_sn + OS_FILE_LOG_BLOCK_SIZE <= log_translate_lsn_to_sn(log.write_lsn.load()) + buf_size_sn); }
      对于整个日志长度大于当前整个 redo log bufferredo log 需要Resize 设置 redo log buffer
  1. m_impl->m_log.for_each_block(write_log); 按块写入buffer
    1. 对m_log中的每一个 512 字节的 Block 调用mtr_write_log_t()(需要注意的是mtr_write_log_t()是运算符()的重载)
      • log_buffer_write() 调用 std::memcpy(ptr, str, len) 进行写入redo buffer 。
      • log_buffer_write_completed()更新log_t中的recent_written,即(start_lsnend_lsn)组成的list
      • add_dirty_blocks_to_flush_list() 假如产生了 redo log,则将数据页的newest_modification修改为end_lsn
      • 假如该 Block 是第一次被修改,就需要插入 Buffer Pool 的flush_list将涉及修改的数据页添加到 Buffer Pool 的flush_list(buf_flush_insert_into_flush_list()).(利用block->page.oldest_modification来判断是否为第一次修改)
       

log_flusher

通知log_flusher线程,log_flusher 线程会调fsync 将REDO刷盘,至此完成了REDO完整的写入过程。
默认情况下innodb_flush_log_at_trx_commit = 1,线程会block 等待需要等REDO完成刷盘。innodb_flush_log_at_trx_commit = 2,只REDO只写入系统Page Cache
 
[译] 去掉云计算和k8s —— 把应用迁移回本地InnoDB 的索引页结构