
UPDATE account
SET balance = balance - 100
WHERE id = 42;表面上,这条 SQL 只是把一行数据改掉。可 MySQL 真正执行它时,并不是先在磁盘上找到那一行,再原地改写。
在默认存储引擎 InnoDB 中,这条语句会跨过 MySQL Server 层与存储引擎层,牵涉数据页、Buffer Pool、undo log、redo log、binlog、事务提交和后台刷盘。它们共同解决三个核心问题:怎样改得快、怎样支持回滚、怎样在崩溃后保持数据一致。
本文讨论 MySQL 8.x、InnoDB、开启 binlog 的典型提交路径。为突出主线,省略锁竞争、索引维护、组提交、doublewrite buffer 等旁支细节。
先给面试官一个简版答案
一条 UPDATE 到达 MySQL 后,Server 层先完成解析、优化并调用 InnoDB。InnoDB 按页访问数据:目标页不在 Buffer Pool 时,先从磁盘读入内存;修改前生成 undo 信息,用于回滚和构造 MVCC 历史版本;随后在 Buffer Pool 中修改记录,使数据页成为脏页,并产生 redo 记录。
如果开启 binlog,事务提交时会通过内部两阶段提交协调 redo log 与 binlog:先让 redo 进入 prepare,再写 binlog,最后提交 redo。这样即使中途崩溃,恢复时也能判断事务应该提交还是回滚。数据页本身不要求在提交时立即刷盘,之后可由后台线程批量写回;崩溃后则依靠已持久化的 redo 重放尚未落盘的修改。
这段话已经覆盖了主干,但要真正答稳,还要知道每一步解决什么问题。
1. 前置步骤:Server 层先决定“怎么执行”
客户端发送 SQL 后,MySQL Server 层会处理连接、权限、语法解析和优化,生成执行计划,再由执行器调用 InnoDB 提供的接口访问记录。
WHERE id = 42 是否能走主键或其他索引,决定了 InnoDB 如何定位目标记录。如果没有合适索引,可能需要扫描更多记录;但无论如何,InnoDB 与磁盘交互的基本单位都不是“某一行”,而是数据页。
本文假设 id 能高效定位记录,并聚焦找到记录之后发生的事情。
2. 为什么不直接修改磁盘
如果每次修改都立即随机定位并改写磁盘数据页,高并发下会产生大量离散 I/O,吞吐量很难接受。InnoDB 因此使用 Buffer Pool 缓存表和索引数据页。
执行器找到目标记录时:
- 如果数据页已经在 Buffer Pool,直接使用内存中的页。
- 如果不在,InnoDB 先把对应磁盘页读入 Buffer Pool。
- 后续读取和修改都针对内存页进行。
这样做并不意味着 UPDATE “只剩内存速度”——定位、加锁、日志写入和必要的磁盘同步仍然有成本——但它把数据文件的随机写从事务提交主路径中移开了。

3. 修改之前:undo log 负责“能够反悔”
InnoDB 修改聚簇索引记录前,会生成相应的 undo 记录。更准确地说,undo log 保存的是撤销本次修改所需的信息,不一定是把旧行完整复制一份。
undo log 主要承担两类职责:
- 事务回滚:事务执行失败或主动执行
ROLLBACK时,InnoDB 根据 undo 信息撤销已经发生的修改。 - MVCC 一致性读:其他事务需要读取较早版本时,可以沿版本链结合 undo 记录构造可见的历史版本。
因此,undo log 回答的是:“如果这次修改不该生效,怎样恢复逻辑上的旧状态?”
它和 redo log 的方向正好相反:undo 用来撤销,redo 用来重做。
4. 真正修改发生在 Buffer Pool
undo 信息准备好后,InnoDB 在 Buffer Pool 的数据页中修改目标记录。内存页与磁盘页此时不再一致,这个内存页就成为脏页。
脏页不会因为“脏”就立即刷盘。InnoDB 会综合脏页比例、checkpoint 推进、可用页数量和系统负载等因素,在合适的时机把它写回数据文件。
于是问题来了:事务已经提交,而脏页还没有落盘,此时服务器突然断电怎么办?
答案是 redo log。
5. redo log:先记录变化,再延后写数据页
redo log 是 InnoDB 的崩溃恢复日志。数据页发生修改时,InnoDB 会生成描述相关页修改的 redo 记录,先进入 redo log buffer;事务提交时,再按配置把相应日志写入并同步到 redo log 文件。
这体现了 WAL(Write-Ahead Logging,预写日志)思想:先保证用于恢复的日志达到要求,再把随机的数据页写入延后。
redo 记录按日志序列持续追加,比每次提交都随机改写不同的数据页更适合磁盘和存储设备。若数据库在脏页刷盘前崩溃,InnoDB 重启时可以重放已持久化的 redo,把数据页恢复到正确状态。
注意,redo log 不是“把 SQL 再执行一遍”,也不应简单等同为 binlog 那样的逻辑操作记录。它服务于 InnoDB 数据页的崩溃恢复。
6. binlog:记录 Server 层的数据变更事件
binlog 属于 MySQL Server 层,不是 InnoDB 私有日志。它记录描述数据库变化的事件,常见用途包括:
- 将源库的数据变更复制到副本;
- 在恢复备份后重放后续事件,实现时间点恢复;
- 为审计、订阅或增量处理提供变更来源。
根据 binlog_format,事件可以采用 STATEMENT、ROW 或 MIXED 形式。因此,把 binlog 一概说成“原样记录 SQL”并不准确;在常见的 ROW 模式下,它记录的是行事件。

| 日志 | 所属层 | 主要记录 | 主要用途 |
|---|---|---|---|
| undo log | InnoDB | 撤销修改、构造旧版本所需的信息 | 回滚、MVCC |
| redo log | InnoDB | 与数据页变更和恢复相关的 redo 记录 | 崩溃恢复、延后数据页刷盘 |
| binlog | Server 层 | 数据库变更事件 | 复制、归档、时间点恢复 |
7. 为什么 redo log 和 binlog 需要两阶段提交
同一个事务既要进入 InnoDB 的 redo log,又要进入 Server 层的 binlog。如果两个日志各写各的,就可能出现不一致:
- redo 已提交、binlog 没有:主库恢复后有这笔数据,副本或时间点恢复却看不到。
- binlog 已写、redo 未提交:副本可能执行了主库最终没有的数据变更。
为协调这两个日志,MySQL 在典型路径中使用内部 XA 式两阶段提交。可以把主线简化为:
- redo prepare:InnoDB 写入该事务的 redo,并将事务置为 prepare 状态。
- write binlog:Server 层把该事务的 binlog 写入日志;是否每次都同步到存储设备受
sync_binlog等配置影响。 - redo commit:Server 层通知 InnoDB 完成提交,在 redo 中记录 commit 状态。

不同崩溃点会怎样
两阶段提交真正的价值,要结合崩溃点来看:
- redo prepare 之前崩溃:事务没有形成可提交状态,恢复时按未提交事务处理。
- redo prepare 完成、binlog 尚未完整写入时崩溃:恢复时找不到匹配的完整 binlog 事务,prepared 事务回滚。
- binlog 已完整写入、redo commit 之前崩溃:恢复时可以通过事务标识确认 binlog 中存在完整事务,随后提交对应的 prepared 事务。
- redo commit 之后崩溃:事务已经提交,按提交状态恢复。
所以,“redo 处于 prepare,重启后一律回滚”是错误的。恢复结果还要看 binlog 中是否存在完整、匹配的事务。
8. 把整条执行链路串起来

以自动提交模式下的一条普通 UPDATE 为例,可以把主线整理为:
- Server 层解析 SQL、选择执行计划并调用 InnoDB。
- InnoDB 通过索引定位记录,必要时把数据页读入 Buffer Pool。
- 对目标记录加必要的锁,并生成 undo 信息。
- 修改 Buffer Pool 中的记录,数据页成为脏页。
- 生成 redo 记录,先进入 redo log buffer。
- 提交阶段让 redo 进入 prepare。
- Server 层写入 binlog。
- InnoDB 完成 redo commit。
- 满足当前持久化配置要求后,Server 层向客户端返回提交成功。
- 后台线程在之后把脏页刷新到数据文件;如果刷盘前崩溃,则重启时用 redo 恢复。
如果关闭自动提交并显式开启事务,那么 UPDATE 语句执行结束并不等于事务已经提交。真正的提交链路发生在后续 COMMIT 时。这是面试回答中经常被忽略的边界。
9. “返回成功就绝不丢数据”需要配置前提
只说“有 redo log,所以提交后一定不丢”并不严谨。可靠性还取决于日志何时真正同步到持久化设备。
两个关键参数是:
innodb_flush_log_at_trx_commit=1:每次事务提交时写并刷 redo log,是完整 ACID 持久性的默认要求。sync_binlog=1:每个 binlog 提交组都同步到磁盘,是 binlog 最安全的设置。
官方文档建议,在使用 InnoDB 事务并开启 binlog、希望获得尽可能高的持久性和一致性时,同时使用这两个值。若为了性能改成更宽松的配置,数据库或操作系统崩溃时可能丢失最近已经返回成功的事务。
此外,存储硬件和操作系统必须正确兑现 flush 请求;如果设备错误地报告“已落盘”,即使参数设置正确,也无法做绝对保证。
10. 三个常见误区
误区一:UPDATE 就是直接改磁盘上的一行
InnoDB 按页进行缓存和 I/O,通常先修改 Buffer Pool 中的数据页,再异步刷回数据文件。
误区二:修改完内存就立即返回成功
事务提交前仍要完成日志提交协议,并满足当前持久化配置要求。显式事务中,单条 UPDATE 执行完甚至还没有进入最终提交。
误区三:redo log 和 binlog 是同一种日志
redo log 属于 InnoDB,服务于数据页崩溃恢复;binlog 属于 Server 层,服务于复制和恢复。两者职责、记录形式和生命周期都不同。
11. 一份可以直接复述的面试回答
InnoDB 执行 UPDATE 时,不会直接随机改写磁盘中的某一行,而是先通过索引定位记录,并确保目标数据页位于 Buffer Pool。修改前生成 undo 信息,支持事务回滚和 MVCC;随后修改内存页,使其成为脏页,同时产生用于崩溃恢复的 redo 记录。
如果开启 binlog,提交阶段会通过两阶段提交协调 redo log 与 binlog:先 redo prepare,再写 binlog,最后 redo commit。这样即使在任一阶段崩溃,恢复时也能结合 redo 状态和完整 binlog 判断事务应提交还是回滚。数据页可以稍后由后台线程刷盘;如果刷盘前宕机,则通过 redo 重放恢复。最终持久性还取决于
innodb_flush_log_at_trx_commit、sync_binlog以及底层存储是否正确执行 flush。
如果面试官继续追问,可以补充两点:显式事务要到 COMMIT 才真正提交;redo 处于 prepare 时不一定回滚,还要检查 binlog 中是否存在完整事务。
总结
一条 UPDATE 的核心不是“把一行改掉”,而是让多个机制围绕同一次修改达成协作:
- Buffer Pool 把数据文件随机写移出事务主路径;
- undo log 让事务能够撤销,并支持一致性读;
- redo log 让尚未刷盘的数据页可以在崩溃后恢复;
- binlog 保存可用于复制和恢复的数据变更事件;
- 两阶段提交让 redo log 与 binlog 对同一事务作出一致判断。
真正理解这条链路之后,Buffer Pool、WAL、MVCC、崩溃恢复和复制一致性就不再是几个孤立名词,而会成为一套完整的事务系统。
评论(0)