Redo Log - 重做日志详解 ​定义 ​Redo Log (重做日志) 是InnoDB存储引擎的一种物理日志,记录数据页被修改后的物理变化。当事务提交或日志缓冲区刷新时,Redo Log会被写入磁盘文件(ib_logfile0和ib_logfile1),确保即使发生系统崩溃,已提交的事务也能在恢复时被重做(Redo),从而保证事务的持久性 (Durability)。

核心特征 ​特征说明日志类型物理日志(Physical Log)记录内容数据页的物理修改(如"在偏移量X处写入Y字节")日志格式MLOG_1BYTE、MLOG_2BYTES、MLOG_4BYTES等文件大小固定大小,循环使用(由innodb_log_file_size控制)文件数量默认2个(ib_logfile0, ib_logfile1)写入时机事务提交、Log Buffer满、每秒刷新、检查点刷盘策略由innodb_flush_log_at_trx_commit参数控制与WAL的关系 ​WAL (Write-Ahead Logging) - 预写日志机制(理论/原则)

↓ 具体实现

Redo Log - 重做日志(InnoDB的实现方式)

↓ 包含

├── 日志缓冲区(Log Buffer) - 内存中的日志

└── 日志文件组(Log Files) - 磁盘上的日志文件123456WAL是理论框架,规定"先写日志后写数据"的原则;Redo Log是具体实现,是WAL机制在InnoDB中的物理载体。

为什么需要Redo Log? ​问题1: 随机IO性能差 ​如果每次修改都直接写入数据页(Data Page):

数据页分散在磁盘不同位置 → 随机IO每次修改都需要fsync()刷盘 → 性能极低假设TPS=1000,则需要1000次随机IO/s解决方案:

将随机IO转换为顺序IO先写Redo Log(顺序追加) → 性能提升10-100倍后台异步刷脏页(Checkpoint机制)问题2: 崩溃恢复 ​如果修改数据页过程中发生崩溃:

数据页可能只写了一半 → 页断裂 (Page Tear)无法区分哪些修改已提交,哪些未提交数据一致性被破坏解决方案:

Redo Log采用**追加写 (Append-Only)**方式,原子性强崩溃后通过Redo Log重做已提交事务通过Undo Log回滚未提交事务性能对比 ​sql-- 测试场景: 插入10万条记录

-- 方案1: 直接修改数据页(禁用Redo Log - 仅用于对比)

SET GLOBAL innodb_flush_log_at_trx_commit = 0;

INSERT INTO test_table SELECT * FROM generate_series(1, 100000);

-- 耗时: ~30秒 (随机IO)

-- 方案2: 使用Redo Log(正常配置)

SET GLOBAL innodb_flush_log_at_trx_commit = 1;

INSERT INTO test_table SELECT * FROM generate_series(1, 100000);

-- 耗时: ~5秒 (顺序IO)

-- 性能提升: 6倍12345678910111213Redo Log的物理结构 ​日志文件组布局 ​ib_logfile0 (默认5MB~512MB) ib_logfile1 (默认5MB~512MB)

┌─────────────────────┐ ┌─────────────────────┐

│ LOG_BLOCK_HDR (12B) │ │ LOG_BLOCK_HDR (12B) │

│ Redo Record #1 │ │ Redo Record #N+1 │

│ Redo Record #2 │ │ Redo Record #N+2 │

│ ... │ │ ... │

│ LOG_BLOCK_TRAILER │ │ LOG_BLOCK_TRAILER │

└─────────────────────┘ └─────────────────────┘

↑ ↑

└──── 循环写入(Log Group) ──────────┘12345678910关键概念:

Log Group: 一组Redo Log文件,InnoDB将其视为一个连续的环形缓冲区Log Block: 每个日志文件被分成512字节的块(与磁盘扇区对齐)LSN (Log Sequence Number): 全局递增的日志序列号,标识日志位置Log Block结构 ​c/* storage/innobase/include/log0log.h */

#define LOG_BLOCK_HDR_SIZE 12 /* 块头大小 */

#define LOG_BLOCK_TRL_SIZE 8 /* 块尾大小 */

#define LOG_BLOCK_DATA_SIZE 512 /* 块总大小 */

#define LOG_BLOCK_MAX_REC_SIZE (LOG_BLOCK_DATA_SIZE - LOG_BLOCK_HDR_SIZE - LOG_BLOCK_TRL_SIZE)

/* Log Block布局 */

struct log_block_t {

/* Header (12 bytes) */

uint32_t block_no; /* 块编号 */

uint16_t data_len; /* 数据长度 */

uint16_t first_rec_group; /* 第一个记录组偏移 */

uint16_t checkpoint_no; /* 检查点编号 */

/* Data (492 bytes) */

byte data[LOG_BLOCK_DATA_SIZE - LOG_BLOCK_HDR_SIZE - LOG_BLOCK_TRL_SIZE];

/* Trailer (8 bytes) */

uint32_t checksum; /* 校验和 */

};1234567891011121314151617181920Redo Record格式 ​每条Redo Record记录一次物理修改:

c/* Redo Record通用格式 */

struct redo_record_t {

uint16_t total_len; /* 记录总长度 */

uint8_t type; /* 记录类型(MLOG_*) */

uint8_t space; /* 表空间ID */

uint32_t page_no; /* 页号 */

uint16_t offset; /* 修改偏移量 */

uint16_t len; /* 修改长度 */

byte data[]; /* 修改的数据内容 */

};

/* 常见记录类型 */

enum mlog_type_t {

MLOG_1BYTE = 1, /* 修改1字节 */

MLOG_2BYTES = 2, /* 修改2字节 */

MLOG_4BYTES = 3, /* 修改4字节 */

MLOG_8BYTES = 4, /* 修改8字节 */

MLOG_REC_INSERT = 5, /* 记录插入 */

MLOG_REC_CLUST_DELETE_MARK = 6, /* 聚集索引删除标记 */

MLOG_LIST_END_DELETE = 7, /* 链表删除结束 */

MLOG_PAGE_CREATE = 11, /* 页创建 */

MLOG_COMP_REC_INSERT = 55, /* 压缩记录插入 */

// ... 共50+种类型

};123456789101112131415161718192021222324InnoDB源码分析 ​Redo Log写入流程 ​cpp/* storage/innobase/log/log0log.cc */

/**

* 将Mini-Transaction的修改写入Redo Log

* @param mtr Mini-Transaction对象

*/

void mtr_commit(mtr_t* mtr)

{

/* 1. 检查是否已提交 */

if (mtr->is_committed()) {

return;

}

/* 2. 获取MTR中的所有日志块 */

mtr_buf_t* log = mtr->get_log();

ulint len = log->len();

if (len == 0) {

mtr->set_state(MTR_STATE_COMMITTED);

return;

}

/* 3. 预留日志空间 */

mutex_enter(&log_sys->mutex);

lsn_t start_lsn = log_sys->lsn;

lsn_t end_lsn = start_lsn + len;

/* 4. 检查是否需要切换日志块 */

if (log_sys->buf_free % LOG_BLOCK_DATA_SIZE + len > LOG_BLOCK_MAX_REC_SIZE) {

/* 填充当前块的剩余空间 */

ulint pad_len = LOG_BLOCK_DATA_SIZE - (log_sys->buf_free % LOG_BLOCK_DATA_SIZE);

memset(log_sys->buf + log_sys->buf_free, 0, pad_len);

log_sys->buf_free += pad_len;

log_sys->lsn += pad_len;

/* 更新块头信息 */

log_block_set_data_len(

log_sys->buf + (log_sys->buf_free - LOG_BLOCK_DATA_SIZE),

LOG_BLOCK_DATA_SIZE);

}

/* 5. 复制日志到Log Buffer */

memcpy(log_sys->buf + log_sys->buf_free, log->data(), len);

log_sys->buf_free += len;

log_sys->lsn += len;

/* 6. 设置最后一个块的长度 */

ulint last_block_offset = log_sys->buf_free % LOG_BLOCK_DATA_SIZE;

if (last_block_offset > 0) {

log_block_set_data_len(

log_sys->buf + (log_sys->buf_free - last_block_offset),

last_block_offset);

}

/* 7. 根据配置决定是否立即刷盘 */

bool flush_to_disk = false;

if (srv_flush_log_at_trx_commit == 1) {

flush_to_disk = true;

} else if (srv_flush_log_at_trx_commit == 2 &&

my_timer_since(mtr->start_timer) >= 1000000) {

/* 每秒刷新一次 */

flush_to_disk = true;

}

mutex_exit(&log_sys->mutex);

/* 8. 刷盘(如果需要) */

if (flush_to_disk) {

log_buffer_flush_to_disk();

}

/* 9. 标记MTR为已提交 */

mtr->set_state(MTR_STATE_COMMITTED);

}1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374Redo Log刷盘策略 ​cpp/* storage/innobase/log/log0log.cc */

/**

* 将Log Buffer刷新到磁盘

*/

void log_buffer_flush_to_disk()

{

mutex_assert_owner(&log_sys->mutex);

/* 1. 获取需要刷新的LSN范围 */

lsn_t flushed_to_disk_lsn = log_sys->flushed_to_disk_lsn;

lsn_t current_lsn = log_sys->lsn;

if (current_lsn <= flushed_to_disk_lsn) {

return; /* 已经全部刷新 */

}

/* 2. 计算需要写入的数据量 */

ulint len = static_cast(current_lsn - flushed_to_disk_lsn);

/* 3. 确定写入位置(处理环形缓冲区回绕) */

ulint from_offset = static_cast(flushed_to_disk_lsn % log_sys->size);

ulint to_offset = from_offset + len;

/* 4. 执行写入操作 */

if (to_offset <= log_sys->size) {

/* 不需要回绕,一次性写入 */

os_file_write(

log_sys->files[0],

log_sys->buf + from_offset,

from_offset,

len);

} else {

/* 需要回绕,分两次写入 */

ulint first_part = log_sys->size - from_offset;

ulint second_part = to_offset - log_sys->size;

/* 写入第一部分 */

os_file_write(

log_sys->files[0],

log_sys->buf + from_offset,

from_offset,

first_part);

/* 写入第二部分(回到文件开头) */

os_file_write(

log_sys->files[1],

log_sys->buf,

0,

second_part);

}

/* 5. 调用fsync确保数据落盘 */

for (ulint i = 0; i < log_sys->n_files; i++) {

os_file_flush(log_sys->files[i]);

}

/* 6. 更新刷新位置 */

log_sys->flushed_to_disk_lsn = current_lsn;

}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960崩溃恢复重做流程 ​cpp/* storage/innobase/log/log0recv.cc */

/**

* 崩溃恢复时的Redo阶段

* @param start_lsn 恢复起始LSN

* @param end_lsn 恢复结束LSN

*/

void recv_recover_page(

buf_block_t* block,

page_t* page)

{

lsn_t page_lsn = page_get_max_lsn(page);

lsn_t current_lsn = log_sys->lsn;

/* 1. 检查页面是否需要恢复 */

if (page_lsn >= current_lsn) {

return; /* 页面已是最新 */

}

/* 2. 从Redo Log中读取需要重做的记录 */

recv_group_t* group = recv_sys->groups;

recv_addr_t* recv_addr;

HASH_SEARCH(addr_hash, group->addr_hash,

calc_hash(block->space, block->page_no),

recv_addr,

recv_addr->space == block->space &&

recv_addr->page_no == block->page_no);

if (recv_addr == NULL) {

return; /* 没有该页面的重做记录 */

}

/* 3. 按LSN顺序应用所有Redo Record */

recv_op_t* op;

UT_LIST_BASE_NODE_CHECK(recv_op_t, recv_addr->recovered_ops, op) {

if (op->lsn <= page_lsn) {

continue; /* 已应用的记录跳过 */

}

/* 4. 根据操作类型应用重做 */

switch (op->type) {

case RECV_MLOG_1BYTE:

page_write_byte(page, op->offset, op->data[0]);

break;

case RECV_MLOG_2BYTES:

page_write_ui16(page, op->offset, mach_read_from_2(op->data));

break;

case RECV_MLOG_4BYTES:

page_write_ui32(page, op->offset, mach_read_from_4(op->data));

break;

case RECV_REC_INSERT:

page_cur_insert_rec_low(page, op->offset, op->data, op->len);

break;

case RECV_PAGE_CREATE:

page_create_low(page, op->data);

break;

default:

ut_error;

}

page_set_max_lsn(page, op->lsn);

}

/* 5. 标记页面为脏页 */

buf_block_set_dirty(block, TRUE);

}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172关键配置参数 ​innodb_log_file_size ​ini# 单个Redo Log文件大小(单位:MB)

# 范围: 4MB ~ 512GB (MySQL 8.0+)

# 默认: 48MB (MySQL 5.7), 2048MB (MySQL 8.0+)

[mysqld]

innodb_log_file_size = 2048M123456影响:

越大越好? 不一定。太大会延长恢复时间太小问题: 频繁触发检查点,增加IO负担推荐值: 写密集型应用: 2GB~4GB普通应用: 512MB~1GB测试环境: 48MB~128MB计算公式:

推荐大小 = (每小时最大写入量 × 保留时间) / 日志文件数量

例如:

- 每小时写入: 10GB

- 希望保留: 1小时

- 文件数量: 2

- 单个文件大小: 10GB / 2 = 5GB1234567innodb_log_files_in_group ​ini# Redo Log文件数量

# 范围: 2 ~ 100

# 默认: 2

[mysqld]

innodb_log_files_in_group = 2123456影响:

InnoDB以循环方式写入日志文件文件越多,单个文件越小(总大小固定)推荐保持默认值2innodb_flush_log_at_trx_commit ​ini# Redo Log刷盘策略

# 0: 每秒刷新并刷盘

# 1: 每次事务提交都刷新并刷盘(默认,最安全)

# 2: 每次事务提交都刷新,但每秒才刷盘

[mysqld]

innodb_flush_log_at_trx_commit = 11234567三种模式对比:

值刷新策略刷盘策略性能安全性适用场景0每秒1次每秒1次最高最低(丢失1秒数据)日志、缓存等非关键数据1每次提交每次提交最低最高(不丢失数据)金融、订单等关键数据2每次提交每秒1次中等中等(丢失1秒数据)折中方案性能测试:

sql-- 测试脚本

CREATE TABLE test_redo (

id INT AUTO_INCREMENT PRIMARY KEY,

data VARCHAR(255)

) ENGINE=InnoDB;

-- 模式0: 约10,000 TPS

SET GLOBAL innodb_flush_log_at_trx_commit = 0;

INSERT INTO test_redo (data) VALUES (REPEAT('a', 255)); -- 重复执行

-- 模式1: 约1,000 TPS

SET GLOBAL innodb_flush_log_at_trx_commit = 1;

INSERT INTO test_redo (data) VALUES (REPEAT('a', 255)); -- 重复执行

-- 模式2: 约5,000 TPS

SET GLOBAL innodb_flush_log_at_trx_commit = 2;

INSERT INTO test_redo (data) VALUES (REPEAT('a', 255)); -- 重复执行1234567891011121314151617innodb_log_buffer_size ​ini# Log Buffer大小(单位:MB)

# 范围: 1MB ~ 16GB

# 默认: 16MB

[mysqld]

innodb_log_buffer_size = 64M123456影响:

越大: 能容纳更多大事务,减少刷新次数太小: 大事务频繁刷新日志,性能下降推荐值: 有大事务(BLOB/TEXT): 64MB~256MB普通应用: 16MB~32MB小事务为主: 8MB~16MBRedo Log工作流程 ​完整的事务提交流程 ​sqlSTART TRANSACTION;

UPDATE accounts SET balance = balance - 100 WHERE id = 1;

UPDATE accounts SET balance = balance + 100 WHERE id = 2;

COMMIT;1234底层执行步骤:

Step 1: 修改数据页(内存)

┌─────────────────────────────────┐

│ Buffer Pool │

│ │

│ Page #100 (accounts.id=1) │

│ balance: 1000 → 900 │ ← 直接在内存中修改

│ │

│ Page #200 (accounts.id=2) │

│ balance: 500 → 600 │ ← 直接在内存中修改

└─────────────────────────────────┘

Step 2: 生成Redo Record(内存)

┌─────────────────────────────────┐

│ Log Buffer │

│ │

│ Redo Record #1: │

│ type=MLOG_4BYTES │

│ space=1, page_no=100 │

│ offset=50, data=0x00000384 │

│ │

│ Redo Record #2: │

│ type=MLOG_4BYTES │

│ space=1, page_no=200 │

│ offset=50, data=0x00000258 │

└─────────────────────────────────┘

Step 3: COMMIT时写入Redo Log(磁盘)

┌─────────────────────────────────┐

│ ib_logfile0 │

│ │

│ LSN 1000: MLOG_4BYTES ... │ ← 持久化

│ LSN 1001: MLOG_4BYTES ... │ ← 持久化

│ LSN 1002: MLOG_COMMIT ... │ ← 标记提交

└─────────────────────────────────┘

Step 4: 返回客户端成功

COMMIT OK!

Step 5: 后台异步刷脏页(Checkpoint)

Buffer Pool中的脏页 → 数据文件(ibd)12345678910111213141516171819202122232425262728293031323334353637383940崩溃恢复场景 ​场景: 事务提交后,数据页还未刷盘,系统崩溃

时间线:

T1: 事务修改Page #100(内存)

T2: 生成Redo Record(内存)

T3: COMMIT, Redo Record写入ib_logfile0(磁盘) ✓

T4: 返回客户端成功 ✓

T5: 系统崩溃! 💥 (Buffer Pool中的数据丢失)

恢复过程:

┌──────────────────────────────────────────┐

│ MySQL重启 │

└──────────────────────────────────────────┘

┌──────────────────────────────────────────┐

│ InnoDB自动启动崩溃恢复 │

│ 1. 读取ib_logfile0和ib_logfile1 │

│ 2. 扫描所有Redo Record │

│ 3. 找到LSN最大的记录 │

└──────────────────────────────────────────┘

┌──────────────────────────────────────────┐

│ Redo阶段(重做) │

│ FOR 每个Redo Record: │

│ IF 记录的LSN > 数据页的LSN THEN │

│ 应用修改到数据页 │

│ END IF │

│ END FOR │

│ │

│ 结果: Page #100恢复到balance=900 │

└──────────────────────────────────────────┘

┌──────────────────────────────────────────┐

│ Undo阶段(回滚) │

│ FOR 每个未提交事务: │

│ 使用Undo Log回滚修改 │

│ END FOR │

└──────────────────────────────────────────┘

┌──────────────────────────────────────────┐

│ 数据库恢复到一致状态 ✓ │

└──────────────────────────────────────────┘12345678910111213141516171819202122232425262728293031323334353637383940实际案例分析 ​案例1: Redo Log满导致性能下降 ​问题现象:

[Warning] InnoDB: Log file ./ib_logfile0 is full

[Warning] InnoDB: Checkpoint age: 850000012原因分析:

innodb_log_file_size设置过小(48MB)大批量INSERT操作产生大量Redo LogCheckpoint速度跟不上日志生成速度InnoDB被迫等待Checkpoint完成解决方案:

ini[mysqld]

# 调整前

innodb_log_file_size = 48M

innodb_log_buffer_size = 16M

# 调整后

innodb_log_file_size = 2048M

innodb_log_buffer_size = 64M

innodb_io_capacity = 2000

innodb_io_capacity_max = 400012345678910验证效果:

sql-- 监控Redo Log使用情况

SHOW ENGINE INNODB STATUS\G

-- 查看输出:

Log sequence number 1234567890

Log buffer assigned up to 1234500000

Log buffer flushed up to 1234400000

Pending log writes 0

-- 计算使用率

使用率 = (1234567890 - 1234400000) / (2048 * 1024 * 1024) ≈ 0.008%1234567891011案例2: 批量导入优化 ​场景: 导入1亿条记录

优化前(耗时2小时):

sql-- 使用默认配置

SET GLOBAL innodb_flush_log_at_trx_commit = 1;

LOAD DATA INFILE '/data/huge_file.csv'

INTO TABLE huge_table

FIELDS TERMINATED BY ',';123456优化后(耗时15分钟):

sql-- 1. 临时调整配置

SET GLOBAL innodb_flush_log_at_trx_commit = 2;

SET GLOBAL innodb_log_buffer_size = 256 * 1024 * 1024; -- 256MB

SET GLOBAL innodb_log_file_size = 4096 * 1024 * 1024; -- 4GB

-- 2. 禁用自动提交

SET AUTOCOMMIT = 0;

-- 3. 分批导入

LOAD DATA INFILE '/data/huge_file.csv'

INTO TABLE huge_table

FIELDS TERMINATED BY ','

ROWS PER BATCH = 100000;

-- 4. 手动提交

COMMIT;

-- 5. 恢复配置

SET GLOBAL innodb_flush_log_at_trx_commit = 1;

SET GLOBAL innodb_log_buffer_size = 16 * 1024 * 1024;1234567891011121314151617181920性能对比:

指标优化前优化后提升总耗时7200秒900秒8倍TPS13,888111,1118倍Redo Log写入50GB50GB-fsync次数1亿次900次11万倍案例3: 崩溃恢复实战 ​故障场景:

2026-04-10 14:30:00 [ERROR] mysqld got signal 11

2026-04-10 14:30:00 [ERROR] Stack trace:

...

2026-04-10 14:30:05 [Note] Starting crash recovery...1234恢复过程:

bash# 1. 查看错误日志

tail -f /var/log/mysql/error.log

# 输出:

InnoDB: Doing recovery: scanned up to log sequence number 1234567890

InnoDB: Doing recovery: scanned up to log sequence number 1234600000

InnoDB: 1 transaction(s) which must be rolled back or cleaned up

InnoDB: Starting in background the rollback of uncommitted transactions

# 2. 监控恢复进度

SHOW ENGINE INNODB STATUS\G

# 输出:

--- LOG ---

Log sequence number 1234600000

Log flushed up to 1234600000

Pages flushed up to 1234500000

Last checkpoint at 1234500000

# 3. 恢复完成

2026-04-10 14:35:00 [Note] InnoDB: Starting final batch to recover pages

2026-04-10 14:35:30 [Note] InnoDB: Last binlog file './mysql-bin.000123'

2026-04-10 14:35:30 [Note] InnoDB: Rollback of non-committed transactions completed

2026-04-10 14:35:30 [Note] InnoDB: Shutdown completed; log sequence number 1234600000

2026-04-10 14:35:31 [Note] mysqld: ready for connections.12345678910111213141516171819202122232425恢复耗时分析:

Redo阶段: 3分钟(扫描500MB日志)Undo阶段: 2分钟(回滚1个大事务)总计: 5分钟优化建议:

ini[mysqld]

# 加快恢复速度

innodb_log_file_size = 2048M # 增大日志文件

innodb_log_buffer_size = 64M # 增大日志缓冲

innodb_flush_log_at_trx_commit = 1 # 保证数据安全

innodb_io_capacity = 2000 # 提高IO能力

innodb_io_capacity_max = 40001234567Redo Log vs Undo Log ​对比项Redo LogUndo Log作用重做已提交事务回滚未提交事务日志类型物理日志逻辑日志记录内容"页X偏移Y处写入Z""将记录A改为B"保证特性持久性(Durability)原子性(Atomicity)文件格式ib_logfile0, ib_logfile1ibdata1(共享表空间)写入时机事务提交时数据修改时删除时机Checkpoint完成后事务提交后(延迟清理)MVCC支持不支持支持(版本链)崩溃恢复Redo阶段使用Undo阶段使用最佳实践 ​1. 配置优化 ​ini[mysqld]

# 生产环境推荐配置

innodb_log_file_size = 2048M # 根据写入量调整

innodb_log_files_in_group = 2 # 保持默认

innodb_log_buffer_size = 64M # 有大事务时增大

innodb_flush_log_at_trx_commit = 1 # 关键业务用1,非关键用2

innodb_io_capacity = 2000 # SSD可调更高

innodb_io_capacity_max = 4000

innodb_max_dirty_pages_pct = 75 # 控制脏页比例1234567892. 监控告警 ​sql-- 监控Redo Log生成速度

SELECT

VARIABLE_NAME,

VARIABLE_VALUE,

(VARIABLE_VALUE - @prev_value) AS diff_per_second

FROM performance_schema.global_status

WHERE VARIABLE_NAME = 'Innodb_os_log_written'

INTO @name, @value, @diff;

SET @prev_value = @value;

-- 告警规则

-- 如果每秒Redo Log生成 > 100MB, 发送告警

-- 监控检查点年龄

SHOW ENGINE INNODB STATUS\G

-- 检查 "Checkpoint age" 值

-- 如果接近 innodb_log_file_size * innodb_log_files_in_group, 需要告警1234567891011121314151617183. 批量操作优化 ​pythonimport mysql.connector

from contextlib import contextmanager

@contextmanager

def batch_import_mode(connection):

"""批量导入优化模式"""

cursor = connection.cursor()

try:

# 1. 临时调整配置

cursor.execute("SET SESSION innodb_flush_log_at_trx_commit = 2")

cursor.execute("SET SESSION autocommit = 0")

yield cursor

# 2. 手动提交

connection.commit()

except Exception as e:

# 3. 异常回滚

connection.rollback()

raise e

finally:

# 4. 恢复配置

cursor.execute("SET SESSION innodb_flush_log_at_trx_commit = DEFAULT")

cursor.execute("SET SESSION autocommit = DEFAULT")

cursor.close()

# 使用示例

connection = mysql.connector.connect(...)

with batch_import_mode(connection) as cursor:

for i in range(1000000):

cursor.execute(

"INSERT INTO large_table (col1, col2) VALUES (%s, %s)",

(i, f"data_{i}")

)

# 每10000条提交一次

if i % 10000 == 0:

connection.commit()12345678910111213141516171819202122232425262728293031323334353637383940414. 故障排查 ​bash# 1. 检查Redo Log是否成为瓶颈

mysqladmin extended-status | grep Innodb_os_log_written

# 2. 检查fsync频率

iostat -x 1 | grep sda

# 关注 await 和 svctm 指标

# 3. 查看InnoDB状态

mysql -e "SHOW ENGINE INNODB STATUS\G" | grep -A 10 "LOG"

# 4. 监控检查点进度

mysql -e "SHOW ENGINE INNODB STATUS\G" | grep "Checkpoint age"123456789101112参考资料 ​MySQL官方文档 ​InnoDB Redo LogInnoDB Crash RecoveryInnoDB Configuration源码文件 ​storage/innobase/log/log0log.cc - Redo Log核心实现storage/innobase/log/log0recv.cc - 崩溃恢复实现storage/innobase/include/log0log.h - 日志系统头文件storage/innobase/mtr/mtr0mtr.cc - Mini-Transaction实现相关术语 ​WAL - 预写日志机制检查点 - Checkpoint机制[Undo Log](./Undo Log.md) - 回滚日志双写缓冲 - Doublewrite Buffer技术博客 ​Jeremy Cole: InnoDB transaction historyFacebook MySQL Blog: InnoDB redo log tuningPercona: Understanding InnoDB log files版本历史:

2026-04-12: 初始版本,详尽讲解Redo Log原理与实践