凌晨两点被电话叫醒,说线上某个表的数据对不上,同一个事务里看起来只提交了一半。打开 MySQL 错误日志一看,实例在几小时前发生过一次非正常重启,重启后 InnoDB 进入了崩溃恢复流程。当时我对着 Binlog、Redo Log、Undo Log 这三个概念翻来覆去排查,才真正意识到:只看 SQL 语法、索引优化根本不够,日志体系才是 MySQL 数据安全的底座。这篇文章我就把 MySQL 日志体系的 Binlog、Redo Log 与 Undo Log 核心原理一次讲透,包括它们各自负责什么、为什么非要都留着、事务提交时内部到底发生了什么,以及那些网上很少说清的高频坑。
1. 先把问题拆开:日志体系到底在解决什么事
1.1 数据库“出事”时一定会问的三个问题
随便翻一个后端面试题,几乎必问:MySQL 断电后为什么数据不丢?事务回滚是怎么实现的?主从复制靠什么同步?这三个问题背后,对应的正是三种不同职责的日志。
第一个问题是“数据持久性”。你执行了一条 UPDATE,MySQL 不可能每次提交都立刻把磁盘上的数据页改掉,那样随机 IO 太慢。它先把变更写到日志里,数据页只是先改在内存 Buffer Pool 里。等系统崩溃,内存里的数据页没了,日志还在,靠它重新把变更“补做”到磁盘上,这就是 Redo Log 的活。
第二个问题是“事务原子性”。事务执行了一半,用户主动 ROLLBACK,或者系统崩溃后要清理那些还没提交的中间状态。数据库需要把已经修改过的数据恢复成原来的样子,靠谁?Undo Log。
第三个问题是“系统整体恢复与复制”。数据库管理员误删了一张表,想恢复到删除之前的某个时间点;或者为了读写分离,要把主库的变更同步到从库。这些场景记录的是“一段一段的历史变更”,需要一份能按时间回放的逻辑日志,这就是 Binlog。
1.2 为什么 MySQL 需要“三层日志”而不是一套日志
很多初学者最容易懵的地方在于:都是日志,为什么搞三套,能不能合并?答案是不能,因为它们的目标完全不同,而且分布在 MySQL 的不同层上。
MySQL 整体可以粗略分成两层:上面的 Server 层负责连接管理、SQL 解析、优化、执行;下面的存储引擎层才真正负责数据怎么存、怎么读。其中 Binlog 是 Server 层生成的,它不关心底层是 InnoDB 还是 MyISAM;而 Redo Log 和 Undo Log 都是 InnoDB 存储引擎自己维护的,换一个存储引擎可能就没有这套机制。
你只需要记住一个简单结论:Binlog 负责“留着这条历史记录给外部用”,Redo Log 负责“让 InnoDB 在崩溃后保住已提交的数据”,Undo Log 负责“让事务能回滚并且让其他人读到旧版本数据”。三者配合,才能实现事务的 ACID 里最关键的两个字母:Atomicity 和 Durability。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Redo Log:InnoDB 的“后悔补做账本”
2.1 Redo Log 到底记了什么
Redo Log 是 InnoDB 引擎里的物理日志,它记录的粒度非常底层:第几个表空间、第几号数据页、从哪个偏移量开始、改成了什么内容。它不在乎你执行的是 UPDATE、INSERT 还是 DELETE,它只关心“数据页上的字节如何变化”。
举个例子,执行:
sql复制UPDATE user SET age = 18 WHERE id = 100;
如果这一行原来在数据页 5 的某个位置,InnoDB 先把该数据页加载到 Buffer Pool,在内存里修改 age 字段,同时生成一条 Redo Log,内容大致是“表空间 1,页 5,偏移量 3000,把 age 字段从 28 改成 18”。这条日志先被写到日志缓冲区,再按策略刷到 Redo Log 文件。
这里最关键的概念叫 WAL,Write-Ahead Logging,先写日志,再写数据。你可以把它理解成记账本:你借钱给别人,会计不会当场把总账本翻出来改,而是先在便签上写“某日借给某人多少钱”,等攒够了一批再正式誊抄到总账。只要便签先记下来,即使总账丢了也能事后补。
2.2 LSN、日志文件组与环形覆盖
Redo Log 文件不是无限的,InnoDB 默认在一组日志文件里循环写,一般两个文件,默认大小从 MySQL 8.0 开始是 innodb_log_file_size = 50331648(48MB 左右,具体看版本)。写满最后一个文件后回到第一个文件开头继续覆盖。
为了知道哪些日志还能覆盖、哪些必须保留,InnoDB 引入了 LSN,Log Sequence Number,日志序列号。你可以把 LSN 理解成一个不断递增的计数器,它同时存在于日志文件、数据页和内存里。系统启动时会比较多个 LSN,看看自己处于哪个位置。
同时还有一个 Checkpoint 机制,也就是检查点。InnoDB 会把内存中已修改但还没刷到磁盘的脏页列表记录下来,Checkpoint 推进意味着“这个 LSN 之前的所有数据页变更已经安全落盘”,那么对应位置的 Redo Log 就可以覆盖了。如果 Checkpoint 一直推不快,Redo Log 文件又很小,数据库就会出现“日志写满、等待刷脏页”的情况,表现为突发性的卡顿甚至报错。
2.3 事务提交时 Redo Log 是怎么落盘的
很多 DBA 都会强调一个参数 innodb_flush_log_at_trx_commit,我用一张表直接给你讲透:
| 参数值 | 行为 | 安全性 | 性能 |
|---|---|---|---|
| 1 | 每次事务提交都调用 fsync 把 Redo Log 刷到磁盘 | 最安全,数据库崩溃最多丢一个事务 | 最慢,但核心业务必须用 |
| 0 | 事务提交时不主动刷盘,依靠后台线程每秒刷一次 | 数据库崩溃可能丢最近一秒内已提交事务 | 最快,适合可容忍丢数据的批量导入 |
| 2 | 事务提交时写入操作系统缓存,每秒再刷到磁盘 | 操作系统本身崩溃会丢数据,MySQL 崩溃一般不丢 | 居中,兼顾部分场景 |
注意,这里所说的“提交”和你理解的 COMMIT 语句完全一致。每次 COMMIT 都会参与一次日志刷盘。如果业务量非常大,每一次提交都 fsync 一次会产生巨大的 IO 压力,所以 InnoDB 还有 Group Commit 优化,把多个并发事务的提交请求合并成一次 fsync。
2.4 Redo Log Buffer 和需要关注的内存参数
事务执行过程中,Redo Log 会先写到内存中的 Redo Log Buffer,默认大小是 16MB,对应参数 innodb_log_buffer_size。如果事务非常大,比如一次性 UPDATE 几百万行,产生的 Redo 量超过了 Buffer 容量,就会在事务提交前提前刷盘。
我做过一次压测,插入 500 万行数据的单事务,Redo 产生的量能到几百 MB,如果 innodb_log_buffer_size 还是默认 16MB,你会发现日志刷盘次数暴增,事务耗时反而变慢。适当调大到比如 64MB~128MB,对大批量事务有明显帮助。但这里务必注意:增大 Buffer 不代表事务更安全,只是减少“不够用被迫提前刷盘”的频率。
2.5 MySQL 崩溃恢复时 Redo Log 做了什么
数据库非正常关闭后重新启动,InnoDB 会进入恢复流程。它先扫描最后一个 Checkpoint 之后的 Redo Log,把其中记录的数据页变更重新应用到磁盘。这个过程就是前滚(Roll Forward),让已经提交但还没来得及刷盘的数据重新生效。
重要细节:Redo Log 里不只有已提交事务的记录,有些事务可能刚写了 Redo,还没 COMMIT,系统就崩了。那这些事务要不要回放?回放完又怎么办?这时就要结合 Undo Log 进行回滚。也就是说,崩溃恢复分两步:先用 Redo Log 把所有物理变更恢复到一个历史一致点,再用 Undo Log 把没有完整提交的事务回滚掉。这部分到后面讲 Undo Log 时再展开。
3. Undo Log:回滚和 MVCC 共同依赖的“旧版本仓库”
3.1 Undo Log 写的不是反操作,而是“旧值”
Undo Log 很容易被误解为“反向日志”,其实更贴切的理解是“变更前后的旧版本记录”。它记录的是事务在修改数据之前,该行原来的值是什么,以及一些必要的指针信息,用于找到更早的版本。
INSERT 操作会生成一条 insert undo log,记录插入的行标识,事务回滚时只需要删除这条新插入的记录即可。UPDATE 或 DELETE 则会生成 update undo log,记录修改前的整行旧值。DELETE 在 InnoDB 中通常不是物理删除,而是给记录打上一个删除标记,真正的物理清除要等 Purge 线程处理,这是为了在事务隔离级别下让其他并发事务依然能读到合理的旧数据。
3.2 利用 Undo Log 实现事务回滚
假设你有这样一个场景:
sql复制START TRANSACTION;
UPDATE account SET balance = balance - 100 WHERE id = 1;
UPDATE account SET balance = balance + 100 WHERE id = 2;
-- 此时发现账号2不存在,业务主动回滚
ROLLBACK;
第二条 UPDATE 可能影响了 0 行,但业务逻辑仍然判定异常,于是执行 ROLLBACK。InnoDB 在回滚时,会顺着事务对应的 Undo Log 链条往回找,把第一条 UPDATE 修改过的 balance 恢复成原来的值。逻辑上很简单,但实现上要处理索引记录、主键回表等多个细节。一条记录可能存在多个索引,回滚时要把所有相关索引都调整回旧状态。
3.3 Undo Log 是 MVCC 实现的核心底座
MVCC,多版本并发控制,是 InnoDB 实现高并发读写的关键。它让人可以在不加锁的情况下读到一致性快照。这个能力靠的就是 Undo Log 上的版本链。
每一行记录上都有两个隐藏字段:一个叫 trx_id,记录最后修改该行的事务 ID;另一个叫 roll_pointer,指向 Undo Log 中的一个旧版本。当不同事务读取同一行时,会根据当前事务的隔离级别和自身事务 ID 构造一个 ReadView(读视图),再沿着版本链找到“自己应该看到的那一版”。
这里我提一个所有新手都会问的点:在可重复读隔离级别下,事务第一次执行 SELECT 时会生成一个 ReadView,之后整个事务都复用这个视图。也就是说,之后其他事务提交了新的修改,这个事务依然只会看到第一次查询时的数据版本。如果你不理解版本链,就很难真正搞懂为什么“可重复读”下另一个事务改了数据、你却看不到。多版本并行控制并不是“查询时加锁挡住别人”,而是用旧版本让并发读和写互不干扰。
3.4 旧版本不会一直存在:Purge 线程
既然 Undo Log 保留了多个版本,那什么时候能删除?答案是:当这个旧版本不再被任何活跃事务需要时。
InnoDB 有一个 Purge 线程,会定期清理那些“已经提交且没有其他事务会读取”的 Undo Log 记录和对应的删除标记记录。但如果你启动了一个超级长的事务,一直不提交,那么它可能持有早期的 ReadView,导致 Purge 线程无法清理比该视图更早的版本。这时候 Undo Log 会不断堆积,最直观的表现是磁盘空间被 Undo 表空间文件占满,比如 ibdata1 或独立 Undo Log 文件越来越大。
我在生产环境遇到过一次特别典型的故障:某个定时任务在可重复读隔离级别下开启了事务,然后循环调用外部接口,每次调用耗时好几秒,整体跑了接近 40 分钟才提交。在这 40 分钟里,线上大量 UPDATE 操作都积累了多个版本,因为那个事务的 ReadView 一直不释放,Purge 线程完全推不动,最后 Undo 表空间暴涨,磁盘告警。
排查这类问题最简单的 SQL 是:
sql复制SELECT trx_id, trx_started,
TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS running_seconds,
trx_mysql_thread_id
FROM information_schema.innodb_trx
ORDER BY trx_started ASC;
看到超过预期时间仍未结束的事务,就要尽快确认是否能杀掉。处理完长事务后,Undo 文件也不会立刻缩小,MySQL 8.0 支持独立的 Undo 表空间,可以在线 truncate 或重新配置,具体看你的部署版本和参数。
3.5 一个特殊场景:自增列回滚为什么不一定连续
很多人问:事务里插入了一条记录,回滚之后,再插入一条,为什么自增 ID 不连续?因为自增 ID 的分配发生在插入时,回滚时 InnoDB 不会把已经分配的自增值退回。这是为了高并发插入的性能考虑,回滚不会真正删除自增计数器的记录,这个现象跟 Undo Log 没有直接关系,但经常被误解。理解事务回滚只是恢复“数据语义”,而不是恢复“所有运行时状态”,这一点很重要。
4. Binlog:Server 层的“全量历史纪录片”
4.1 Binlog 和 Redo Log 完全不是一回事
Binlog 在 Server 层生成,记录的是逻辑变更。MySQL 8.0 之前默认格式是 STATEMENT(记录 SQL),现在官方越来越推荐 ROW(记录变更前后的行数据),后续版本默认已是 ROW。它也用于主从复制和基于时间点的恢复。
首先要厘清:Binlog 不能替代 Redo Log。Redo Log 是 InnoDB 自己维护的,属于物理日志,目的是让 InnoDB 存储引擎崩溃后能恢复数据页;Binlog 属于 MySQL Server 层,记录的是逻辑操作,用于复制和数据恢复,它不知道数据页内部是怎么组织的。在事务提交时,MySQL 会通过两阶段提交保证 Redo Log 和 Binlog 一致。复制链路里,从库读取主库 Binlog,再在自己的存储引擎上重新执行这些变更;主库如果发生崩溃,也需要 Binlog 完成基于时间点的数据恢复。
4.2 Binlog 三种格式怎么选
Binlog 格式主要有三种:STATEMENT、ROW、MIXED。
STATEMENT 格式记录的是原始 SQL。它对磁盘空间友好,一条 UPDATE 可能影响数万行,但也只记录一条语句。问题在于,某些 SQL 在主库和从库上执行的结果可能不一样,比如使用了 NOW()、UUID() 或者依赖数据库当前状态的语句。举个例子:
sql复制UPDATE user SET last_login = NOW() WHERE id = 1;
主库和从库执行的时间点不同、系统时间不同,last_login 写入的值就可能不一致。用 STATEMENT 做复制时,需要格外小心这类非确定性函数。
ROW 格式记录的是每一行变更前后的完整镜像。它最安全、最准确,也是我强烈推荐在生产使用的格式。它的主要缺点是日志量会比较大。比如同样一条 UPDATE 影响了 10 万行,ROW 格式可能产生几十 MB 的 Binlog,而 STATEMENT 只有几 KB。如果你做过基于 Binlog 的数据闪回,基本都是依赖 ROW 格式,因为没有行级旧值就很难恢复。
MIXED 格式由 MySQL 自动判断:如果语句是确定性的,使用 STATEMENT;如果可能不确定,会自动切换成 ROW。它兼顾空间和准确性,但在排查问题时相对不够直接。
我一直建议核心业务环境直接固定使用 ROW:
sql复制-- 查看当前格式
SHOW VARIABLES LIKE 'binlog_format';
-- 全局设置
SET GLOBAL binlog_format = 'ROW';
MySQL 8.0 中即使默认格式是 ROW,实际版本不同可能仍有细微差异,务必在部署时确认。
4.3 两阶段提交:为什么 Binlog 和 Redo Log 不能各写各的
如果你只改一行数据,事务提交时既会写 Redo Log,又会写 Binlog。如果 MySQL 先写 Redo Log 然后崩溃,还没写 Binlog,重启后 Redo 认为事务已提交,数据页恢复了,但从库或恢复人员看到的 Binlog 里却没有这条记录,数据和备份就不一致了。如果先写 Binlog 后写 Redo,崩溃后 Binlog 有记录但 Redo 没有,恢复也会乱。
所以 InnoDB 和 Server 层通过“两阶段提交”来解决这个跨组件写入一致性问题。简单理解事务提交过程:
- InnoDB 将事务的 Redo Log 写入日志缓冲区,并标记为 prepare 状态。
- Server 层写入 Binlog,并调用 fsync 持久化。
- InnoDB 将 Redo Log 更新为 commit 状态。
如果崩溃发生在第 1 步和第 2 步之间,重做时发现 Redo Log 是 prepare 但没有匹配的 Binlog,事务会被回滚。如果崩溃发生在第 2 步之后,Binlog 已经存在,恢复时发现 Redo Log 是 prepare 并且 Binlog 有对应记录,就继续把事务提交,保证最终一致。你可以把这个过程想象成买东西时“先签单,再付款,最后盖章”。只有单子、付款、盖章整体校验通过,才算完成。
4.4 Binlog 可以删除吗?到底怎么清理最安全
搜索热词里关于“mysql8.0 binlog可以删除吗”的讨论非常多,可见大家被 Binlog 占满磁盘折腾过。直接回答:可以删除,但不能随手用 rm 删,也不要想着关掉 Binlog。
Binlog 是追加写的连续文件。它在主从复制中扮演传输数据的作用。如果你把某个还没被从库拉取完的 Binlog 删了,从库再来要这个文件时发现不存在,复制就会中断,甚至可能需要重建从库。所以清理 Binlog 必须走 MySQL 提供的命令,它内部会同时维护索引文件,避免破坏复制关系。
最常用的清理方法有几种:
sql复制-- 删除指定文件之前的所有 Binlog
PURGE BINARY LOGS TO 'mysql-bin.000010';
-- 删除指定时间之前的所有 Binlog
PURGE BINARY LOGS BEFORE NOW() - INTERVAL 3 DAY;
-- 查看当前有哪些 Binlog
SHOW BINARY LOGS;
在 MySQL 8.0 里,手动清理之外还可以设置自动过期。MySQL 8.0 之前常用 expire_logs_days,8.0 之后推荐使用 binlog_expire_logs_seconds,设置为秒数:
sql复制SET GLOBAL binlog_expire_logs_seconds = 604800; -- 保留7天
需要注意,自动过期机制只清理文件,不会触发从库断连逻辑。但如果你设置了过短的时间,碰到从库长时间停机再恢复,依然可能出现找不到 Binlog 的情况。所以 Binlog 保留时间要根据全备周期和业务可容忍恢复粒度综合判断,建议至少覆盖一个全量备份周期以上。
4.5 用 mysqlbinlog 进行时间点恢复
提到 Binlog,就不能不提 mysqlbinlog 工具。最经典的用法是配合全量备份做时间点恢复。假设你今天早上 10 点做了一次全量备份,结果中午 12 点有人误删了一张表。恢复思路是:先恢复到 10 点备份,再把 10 点到 12 点之间的 Binlog 重新回放。
回放前先确认好用哪个 Binlog 文件和日志位置。查看 Binlog 内容:
bash复制mysqlbinlog --no-defaults --base64-output=DECODE-ROWS -v /var/lib/mysql/mysql-bin.000012
如果恢复了某个备份文件,但不知道该从哪个位置继续,可以用时间范围粗略定位:
bash复制mysqlbinlog --no-defaults \
--start-datetime="2025-01-20 10:00:00" \
--stop-datetime="2025-01-20 11:59:59" \
/var/lib/mysql/mysql-bin.000012 | mysql -uroot -p
更精确的玩法是按位置号定位。先用上面的命令查找到误删语句前后的 Position,然后指定 --start-position 和 --stop-position 只回放目标区间,避免把误删语句本身再执行一遍。
我在这里要特别提醒一句:基于 Binlog 的时间点恢复是“重新执行一遍历史操作”,如果误操作是 DELETE 或者 DROP,回放时同样会把删除再执行一次。所以恢复时通常找到误操作前的最后一个位置作为终点,而不是直接回放到当前。
5. Redo Log、Undo Log、Binlog 横向对比与高频辨析
5.1 一张表看清三者核心区别
很多人在面试中张口就能说出三者名称,但被问到细节就模糊。我从多个维度整理了一张对比表,建议收藏。
| 对比维度 | Redo Log | Undo Log | Binlog |
|---|---|---|---|
| 所在层级 | InnoDB 存储引擎层 | InnoDB 存储引擎层 | MySQL Server 层 |
| 日志类型 | 物理日志,记录数据页变化 | 逻辑日志,记录数据旧版本 | 逻辑日志,记录 SQL 或行镜像 |
| 主要作用 | 崩溃恢复,保证已提交事务不丢失 | 事务回滚,配合 MVCC 实现一致性读 | 主从复制,基于时间点恢复 |
| 写入时机 | 数据页修改时生成,提交时刷盘 | 数据页修改时生成 | 事务提交时写入 |
| 存储方式 | 固定大小日志文件,循环覆盖 | Undo 表空间,需 Purge 清理 | 追加写入,按配置自动或手动清理 |
| 事务提交关系 | 两阶段提交中的 prepare/commit 状态 | 每行修改都会记录旧版本 | 与 Redo Log 做两阶段提交 |
| 是否可以用于恢复误删 | 不能 | 主要用于事务回滚,不直接用于误删恢复 | 可以,配合备份做时间点恢复 |
这张表只解决“它是什么”,真正容易混淆的问题我单独展开讲。
5.2 为什么崩溃恢复不能用 Binlog 替代 Redo Log
这个问题几乎每年都有面试官问。表面原因是 Redo Log 是物理日志、Binlog 是逻辑日志,实际原因还要更深一层。
崩溃恢复的前提是,InnoDB 能在重启后精确知道哪些数据页没来得及刷盘,并且把这些页面恢复到提交时刻的物理状态。Redo Log 记录的是数据页本身的字节变更,具备极高的重放效率;而且 Redo Log 是循环写在一组固定文件中的,InnoDB 启动时通过 LSN 能快速定位恢复起点。Binlog 记录的是类似“对某一行执行了什么逻辑操作”的内容,它重放时还需要走完整的 SQL 执行链路,效率低得多,也无法处理事务内部那些只是部分写入的物理页状态。
更关键的是,Binlog 在事务提交时才由 Server 层写入,但事务执行过程中产生的 Redo Log 已经记录了数据页上的一切变化。崩溃恢复阶段,InnoDB 需要的是和自身数据页强绑定的物理记录,而不是一份逻辑操作日志。没有 Redo Log,只靠 Binlog,遇到半页写或数据页损坏时根本无法保证能把页恢复到一致状态。
5.3 MVCC、长事务和 Undo Log 纠缠不清的常见误区
提到 Undo Log 和 MVCC,我见过不少开发者把“可重复读”理解成“事务开始时会锁住所有读过的数据”,这是不对的。可重复读的核心就是读视图加版本链:每次 SELECT 返回的都是同一个版本的快照,哪怕本事务里对同一行做了修改,看到的规则会稍微复杂,但总体思想是一致的。
另外一个误区是:“Undo Log 是日志,应该不会占太大空间。”实际上 Undo Log 在事务并发和长事务场景下占用的磁盘空间非常可观。如果你发现 information_schema.innodb_trx 里有长时间运行的事务,第一时间就要警惕 Undo 膨胀。不要等到磁盘满了再处理,提前监控长事务才是治本方案。
6. 故障排查、参数调优与面试真题实录
6.1 故障现场:MySQL 崩溃后无法启动,日志能告诉我们什么
有一次客户现场 MySQL 在非正常断电后启动失败,错误日志里持续出现类似 “InnoDB: Database page corruption” 或者 “Log sequence number is in the future” 这样的信息。我当时的排查思路是:
先看错误日志,确认 Redo Log 是否损坏。如果有备份且能接受丢失部分数据,可以尝试把损坏的日志文件移走,让 InnoDB 重新初始化日志。但这是最后手段,因为做不好会导致更多数据页不一致。
再看是否有从库或者最近的全量备份。如果 Redo Log 损坏且无法修复,优先使用备份恢复。这也是我一直强调监控和演练备份的原因。日志体系再强大,也不能替代一个有效、经过验证的可恢复备份。
在实际操作里,可以执行 SHOW ENGINE INNODB STATUS 查看 InnoDB 的运行状态、Purge 线程情况、当前日志写入位置等信息。如果看到大量 “History list length” 很大,基本就是 Undo Log 清理跟不上,要重点排查长事务。
6.2 Binlog 导致磁盘满怎么办
线上磁盘告警最常见的原因之一是 Binlog 文件堆积。此时不要慌,按顺序处理:
- 先
SHOW BINARY LOGS看当前有多少 Binlog。 - 确认当前实例有没有从库正在拉日志。如果在从库复制正常的情况下,可以比较主从位点,确认哪些 Binlog 已不再需要。
- 使用
PURGE BINARY LOGS清理旧文件。 - 检查
binlog_expire_logs_seconds是否设置合理。 - 改完参数后,如果实例还开着 Binlog,务必确认 Binlog 所在目录剩余空间够用。
有个注意点:不能直接在操作系统层用 rm 删除 Binlog 文件,因为 mysql-bin.index 索引里还记录着文件列表,MySQL 启动或切换日志时会找不到文件,直接报错。
6.3 高频面试题速答:三大日志的区别与协作流程
我把面试里最高频的几个问题浓缩整理如下。
问:一个 UPDATE 语句从提交到落盘,日志层面的流程是什么?
答:存储引擎先修改 Buffer Pool 中的数据页,生成 Redo Log 到日志缓冲区;生成 Undo Log 记录旧版本。事务提交时进入两阶段提交:Redo Log 先标记 prepare,然后写 Binlog 并落盘,最后把 Redo Log 标记为 commit;后台线程根据策略把脏页刷入磁盘。
问:Redo Log 和 Binlog 到底是双写还是一致性协议?
答:两者写入内容不同,靠两阶段提交实现跨组件的一致性。不是简单的“写两次”,而是用 prepare、binlog、commit 的状态判断来兜底崩溃场景。
问:MVCC 在可重复读下为什么不会读到别的事务新提交的数据?
答:因为事务第一次 SELECT 时生成了 ReadView,之后一直沿用这个视图。别的事务提交的版本事务号不在该视图的可见范围内,查询引擎就会沿着 Undo Log 版本链找到符合可见范围的旧版本。
问:数据库突然断电,哪些日志能保住数据?
答:Redo Log + Binlog 是核心。Redo Log 通过 WAL 保证已提交事务的持久性;Binlog 用于复制和恢复;Undo Log 在崩溃恢复时用于回滚未提交事务。
如果你能把这些问题用自己的话复述清楚,而不是背答案,说明你真正理解了三套日志的协作关系。
6.4 从实操中总结的几条关键经验
最后说几个我踩过的、比理论更实际的经验。
第一,不要为了提升性能把 innodb_flush_log_at_trx_commit 直接设成 0。一次压测或数据导入场景可能没事,但如果业务含有订单、支付、账户这类数据,断电丢一秒就意味着事故。我之前管理的一个内部系统就把参数调成 0 做导入,结果机柜意外断电,重启后发现最近一两秒内“已提交”的数据丢了,最后还是结合 Binlog 才勉强补回大部分。非核心业务可以优化,核心业务请坚守值为 1。
第二,Redo Log 文件大小不要设太小。现代 SSD 环境下,innodb_log_file_size 可以按需调大到 1GB 甚至更大,能显著减少日志切换和检查点带来的抖动。但是如果太大,崩溃恢复时间也会变长,需要根据实例的数据变更量来平衡。一般默认值对于小型业务足够,大型业务建议观察 SHOW ENGINE INNODB STATUS 中日志写入相关指标后再调。
第三,Binlog 保留天数对外卖的恢复来说太短是一种“慢性自杀”。公司的历史数据可能几个月后才发现某条数据有问题,如果 Binlog 只留 3 天,基本丧失回溯能力。至少保留一周以上,有条件的话两周,同时把跨机房的备份做好。
第四,把 undo 表空间监控加进你的数据库巡检项。单独看 CPU、内存、连接数并不够,information_schema.innodb_trx 的长事务、SHOW ENGINE INNODB STATUS 里的 History list length,都应该纳入日常巡检指标。
这篇文章从问题场景出发,把 MySQL 日志体系的 Binlog、Redo Log、Undo Log 应用场景、底层原理、故障排查都串讲了一遍。这些东西不一定马上用得上,但一旦你的数据库出事,它们就是最后能抓住的救命稻草。弄懂日志体系,比会背几十条优化 SQL 更有长期价值。
