redo-log刷盘策略#
一句话答案#
redo log 落盘由
innodb_flush_log_at_trx_commit控制(0=每秒刷、1=每次提交都刷盘、2=每次提交写 OS 缓存每秒刷盘),binlog 落盘由sync_binlog控制(0=由 OS 决定、1=每次提交都 fsync、N=攒 N 次再刷);两者都设 1 就是”双 1 配置”,保证宕机不丢已提交事务,是金融级最高可靠性,代价是每次提交两次 fsync 性能最低。
核心要点
innodb_flush_log_at_trx_commit(控制 redo log):
| 值 | 提交时行为 | 宕机影响 | 性能 |
|---|---|---|---|
| 0 | 只写 redo log buffer,后台每秒写 page cache 并 fsync | MySQL 进程崩溃就丢最近 1 秒 | 最快 |
| 1(默认) | 写 page cache 并立即 fsync 到磁盘 | 不丢任何已提交事务 | 最慢、最安全 |
| 2 | 写到 OS page cache,不主动 fsync,靠后台每秒 fsync | MySQL 崩溃不丢(已在 OS),主机宕机/断电丢最近 1 秒 | 折中 |
sync_binlog(控制 binlog):
| 值 | 行为 | 风险 |
|---|---|---|
| 0 | 写 page cache,由 OS 决定何时 fsync | 宕机可能丢多个事务的 binlog |
| 1 | 每次提交都 fsync binlog | 不丢,最安全 |
| N | 累积 N 个事务再 fsync | 宕机最多丢 N 个事务 |
关键认识:
- WAL(Write-Ahead Logging):先写日志再改数据页,提交时只需顺序写 redo log(顺序 IO 快),脏页可异步刷盘
- 双 1 =
innodb_flush_log_at_trx_commit=1+sync_binlog=1:每次事务提交 2 次 fsync(redo prepare 阶段 + binlog),配合两阶段提交保证 crash-safe 且主从一致 - 0 和 2 的区别:0 是 MySQL 崩溃就丢;2 是数据已交给 OS,只有主机断电才丢
组提交(双 1 下性能的救命稻草):
双 1 每个事务都要 fsync,单看很慢,但实际高并发下 InnoDB 用组提交把一批并发事务的 fsync 合并成一次,所以双 1 的吞吐没有想象中那么低。
- 提交分 Flush / Sync / Commit 三阶段流水线,每阶段一个队列、第一个进入的事务当 leader 带头,后续 follower 挂队尾
- Sync 阶段 leader 对整组 binlog 做一次 fsync,N 个事务摊一次 fsync——这是省 IO 的核心
binlog_order_commits=ON保证 binlog 写入顺序 == redo 提交顺序,主从才一致- 可用
binlog_group_commit_sync_delay故意等几微秒攒更大的批,用一点延迟换更高吞吐
面试回答(2分钟版)
InnoDB 用 WAL 机制,事务执行时数据页的修改先写 redo log,提交时只要把 redo log 持久化就算事务安全了,脏页可以慢慢异步刷,这样提交路径上是顺序 IO 很快。但”redo log 写到哪一层”由
innodb_flush_log_at_trx_commit控制:设 0 是只写 redo log buffer,后台每秒才刷盘,MySQL 一崩就丢最近 1 秒;设 1 是默认值,每次提交都直接 fsync 到磁盘,一条已提交事务都不丢,最安全也最慢;设 2 是提交时写到操作系统的 page cache 但不主动 fsync,MySQL 进程崩溃不会丢(数据在 OS 手里),但主机断电会丢最近 1 秒,是性能和安全的折中。binlog 那边对应的参数是
sync_binlog:0 由 OS 决定刷盘时机、1 每次提交都 fsync、N 是攒 N 个事务再刷。两个参数都设成 1 就是业界说的”双 1 配置”,意味着每次提交都要分别把 redo log 和 binlog 刷到磁盘,配合两阶段提交,既保证宕机不丢已提交事务,又保证 redo log 和 binlog 一致、主从不会错乱,是金融、订单这类不能丢数据场景的标配。代价是每次提交两次 fsync,写性能最低。如果是日志、埋点这种允许丢一两秒的场景,常把它们调成 0 或 2、sync_binlog 设成 100~1000 来换吞吐。
追问与易错
追问方向:
- “双 1 为什么是两次 fsync,不是一次?”→ 两阶段提交里:写完 redo log 进入 prepare 要 fsync 一次(受 flush_log_at_trx_commit 控制),写完 binlog 再 fsync 一次(受 sync_binlog 控制),两次都落盘才能保证 crash 后能用两个日志对齐事务状态
- “设 0 和设 2 宕机表现差在哪?”→ 0 是 redo 还在 MySQL 自己的 buffer 里,MySQL 进程崩溃就丢;2 是已经交给操作系统 page cache,MySQL 崩溃不丢,只有整台机器断电/宕机才丢。所以 2 比 0 安全
- “性能瓶颈在哪,怎么缓解?”→ 瓶颈是每次提交的 fsync 系统调用。MySQL 用 组提交(group commit) 把多个并发事务的日志合并成一次 fsync 来摊薄开销;也可适当用 sync_binlog=N 牺牲一点安全换吞吐
- “线上一般怎么配?”→ 核心交易库双 1;从库或可容忍少量丢失的库常用 flush_log_at_trx_commit=2 + sync_binlog=100 提升性能
易错点:
- ❌ 把 flush_log_at_trx_commit=0 和 =2 当成一样——0 丢在 MySQL 崩溃,2 只丢在主机断电
- ❌ 以为只调 redo log 参数就安全——binlog 的 sync_binlog 不设 1,主从复制仍可能丢 binlog 导致不一致
- ❌ 忽略组提交——以为双 1 下每个事务都独立 fsync,实际上高并发时多个事务会合并刷盘