一条 UPDATE 经历的日志写入

完整的写入顺序:
1. 客户端发 UPDATE
2. Server 层:查询数据
3. InnoDB:数据页从磁盘读入内存
4. InnoDB:写 undo log(记录旧版本)
5. InnoDB:更新内存中的数据页
6. InnoDB:redo log 写入内存(prepare 状态)
7. Server:binlog 写入内存
8. InnoDB:redo log 标记为 commit
为什么”先写 redo log,再写 binlog”?因为 redo log 是 InnoDB 的”决断点”——redo 落盘前系统崩溃 = 数据未提交;redo 落盘后、binlog 落盘前崩溃 = 服务恢复时按 redo 重放 + 补 binlog。
如果先写 binlog,写完就无法撤回,可能已被同步到备库,造成主从不一致。
redo log(重做日志)
作用:崩溃恢复 + 持久性(D in ACID)
特点:
- 物理格式日志,记录”在哪个数据页上做了什么修改”
- 顺序写入 redo log file(顺序 I/O,性能远好于随机 I/O)
- 事务执行过程中就写,不需要等事务 commit(“日志优先于数据”)
- 对应脏页落盘后,redo log 空间可被覆盖重用(循环写,类似 ring buffer)
两部分组成
- redo log buffer:内存中的日志缓冲区,默认 8M
- redo log file:磁盘上的日志文件(循环写)
SHOW VARIABLES LIKE 'innodb_log_buffer_size'; -- 8M
刷盘策略 innodb_flush_log_at_trx_commit
| 值 | 行为 | 安全性 | 性能 |
|---|---|---|---|
| 0 | Master Thread 每秒从 buffer 写入 OS cache + flush 磁盘 | 最差(最多丢 1s 数据) | 最好 |
| 1(建议) | 每个事务 commit 时写入 OS cache + flush 磁盘 | 最好(不丢已提交事务) | 一般 |
| 2 | 写入 OS cache,每秒 flush 磁盘 | 中等(OS 崩溃时丢 1s 数据) | 较好 |
💡 生产配置建议:
innodb_flush_log_at_trx_commit=1+sync_binlog=1,数据最安全(最差情况下也不会丢数据)。
LSN
LSN(Log Sequence Number):InnoDB 内部单调递增的版本号。
- 数据页和 redo log 都有 LSN
- 崩溃恢复时,根据 LSN 差值判断”需要重放哪段 redo log”
undo log(回滚日志)
作用:事务回滚 + MVCC + 一致性读
特点:
- 逻辑日志:记录的是”怎么把数据改回去”,而不是物理修改
- 事务开始前就生成
- 事务 commit 后不立即删除——放入待清理链表,由 purge 线程判断其他事务是否还需要历史版本
- 这是 MySQL 的 REPEATABLE READ 能实现”可重复读”的根本原因
模拟示意(更新 name='a' → name='b'):
-- 将要执行的 SQL
UPDATE xxx SET name = 'b';
-- undo log 记录
UPDATE xxx SET name = 'a';
binlog(归档日志)
作用:主从复制 + 数据恢复 + 数据闪回
特点:
- MySQL Server 层产生(与存储引擎无关)
- 逻辑日志,记录数据变化
- 追加写(不覆盖),可用来恢复任意时间点的数据
刷盘策略 sync_binlog
| 值 | 行为 |
|---|---|
| 0 | OS 自动控制刷盘(性能最好,最多丢 1s 数据) |
| 1(建议) | 每个事务 commit 即刷盘 |
| N | 每 N 个事务刷盘一次 |
三种格式对比
| 格式 | 优点 | 缺点 | 场景 |
|---|---|---|---|
| statement(5.7 之前默认) | 日志小,节约 I/O | 含 UUID() 等非确定性函数时主从不一致 | 简单业务 |
| row(5.7+ 默认) | 主从一致性强 | 日志大(每行变更都记) | 数据准确性要求高 |
| mixed | 自适应选择 | 行为不可控 | 不推荐 |
SHOW VARIABLES LIKE 'binlog_format'; -- 查看当前格式
SET binlog_format = 'ROW'; -- 动态修改(需 SUPER 权限)
-- row 格式下记录行级前后镜像
SET GLOBAL binlog_row_image = 'MINIMAL'; -- 只记修改列的后镜像,省空间
Statement vs Row 复制影响
Statement-Based Replication (SBR):
- ✓ 日志量小,节约 I/O
- ✓ 不强制要求主从表结构完全一致
- ✗ 非确定性事件(如
UUID()、NOW())主从不一致 - ✗ 从库批量更新锁范围更大
Row-Based Replication (RBR):
- ✓ 主从一致性最强
- ✓ 减少从库锁使用
- ✗ 表结构必须完全一致
- ✗ 不会触发从库因 SQL 变更而设计的触发器
- ✗ 日志量大
查看 binlog
# 查看 row 格式日志(-vv 展示完整 SQL)
mysqlbinlog -vv mysql-bin.000001
# 查看 statement 格式日志
mysqlbinlog mysql-bin.000001
其他日志简表
| 日志 | 配置项 | 默认 | 用途 |
|---|---|---|---|
| error_log | log_error | 数据目录 | 启动/运行/停止错误,建议与数据目录分离 |
| error_log verbosity | log_error_verbosity | 2(error+warning) | 1=error;2=error+warning;3=+note |
| general_log | general_log=ON/OFF | OFF | 记录所有请求,性能杀手,只在排查时短暂开启 |
| slow_query_log | slow_query_log=ON/OFF | OFF | 慢查询日志 |
| slow_query threshold | long_query_time | 10s | 慢查询阈值,生产建议设 100ms |
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 0.001; -- 100ms
下一篇把”事务”讲透:四种隔离级别、Spring 事务传播机制、多数据源事务管理。