[小技巧53]一条 UPDATE 语句在 MySQL 8.0 中的完整执行旅程
一、SQL 解析与预处理(Parser & Preprocessor)
本文将以 UPDATE users SET balance = balance - 100 WHERE id = 123; 为例
当客户端发送 SQL 请求后,MySQL Server 层首先进行语法和语义分析:
- 词法与语法解析:将 SQL 字符串转换为内部抽象语法树。若语法错误(如关键字拼写错误),在此阶段即返回错误。
- 预处理校验:
- 验证表
users是否存在; - 检查列
balance和id是否属于该表; - 确认当前用户是否拥有
UPDATE权限。
- 验证表
此阶段不涉及存储引擎,完全由 Server 层完成。
二、查询优化(Query Optimization)
优化器基于统计信息(如索引基数、表行数)生成最优执行计划:
- WHERE 条件分析:识别
id = 123为等值条件。 - 索引选择:若
id是主键或唯一索引,则选择主键索引(聚簇索引)进行精确查找;否则可能退化为全表扫描。 - 访问路径确定:决定使用何种方式定位目标行(如 Index Unique Scan)。
- 执行计划缓存:若启用了 Prepared Statement 或 Query Cache(MySQL 8.0 已移除 Query Cache),可能复用已有计划。
优化结果将传递给执行器,用于后续调用存储引擎接口。
三、事务与锁机制(Transaction & Locking)
InnoDB 以事务为单位管理数据修改,并通过多版本并发控制(MVCC)与锁机制保障一致性:
- 事务启动:若当前会话未显式开启事务,
UPDATE会隐式开启一个自动提交事务。 - 行锁获取:
- 若
id为主键,InnoDB 对聚簇索引中id=123的记录加 X 锁(排他锁); - 若使用二级索引,还需回表加锁;
- 在可重复读(REPEATABLE READ)隔离级别下,若
WHERE条件不命中任何行,仍可能加 间隙锁(Gap Lock) 防止幻读。
- 若
- MVCC 支持:旧版本数据通过 Undo Log 构建,供其他事务读取快照。
注:MySQL 8.0 默认隔离级别为 REPEATABLE READ。
四、InnoDB 数据修改流程
一旦锁定目标行,InnoDB 开始修改数据,涉及多个内存与日志结构:
-
Buffer Pool 查找:
- 若目标页已在 Buffer Pool 中,直接修改内存中的页;
- 否则,从磁盘加载数据页至 Buffer Pool(可能触发 LRU 淘汰)。
-
Change Buffer(仅适用于非唯一二级索引):
- 本例中若
balance有非唯一二级索引,其更新可暂存于 Change Buffer,延迟合并以减少随机 I/O。 - 但主键更新或唯一索引不适用 Change Buffer。
- 本例中若
-
Redo Log 写入:
- 记录物理日志(如“将页 X 的偏移 Y 处改为 Z”),用于崩溃恢复;
- 日志先写入 Redo Log Buffer,事务提交时根据
innodb_flush_log_at_trx_commit配置刷盘。
-
Undo Log 生成:
- 保存修改前的
balance值,构成历史版本链; - 支持回滚与 MVCC 快照读;
- Undo Log 本身也受 Redo 保护。
- 保存修改前的
五、二进制日志(Binlog)与两阶段提交(2PC)
为支持主从复制与 PITR(时间点恢复),MySQL 启用 Binlog 时采用 两阶段提交 协调 InnoDB 与 Binlog:
-
Prepare 阶段:
- InnoDB 将事务状态设为 PREPARE,并刷 Redo Log(含 Undo 信息);
- 此时事务尚未对外可见。
-
Write Binlog:
- Server 层将逻辑日志(如
UPDATE的完整语句或行格式变更)写入 Binlog 缓冲区,并刷盘(取决于sync_binlog)。
- Server 层将逻辑日志(如
-
Commit 阶段:
- InnoDB 收到 Server 层通知,将事务标记为 COMMIT,并释放行锁;
- Redo Log 中事务状态更新为已提交。
两阶段提交确保了即使在崩溃后,也能通过 Redo Log 与 Binlog 的一致性判断事务是否应重做或回滚。
六、提交或回滚的最终处理
- 正常提交:释放所有锁,清理事务上下文,Buffer Pool 中的脏页由后台线程(如 Page Cleaner)异步刷盘。
- 显式/隐式回滚:利用 Undo Log 逆向恢复数据,并释放锁。
- 崩溃恢复:启动时通过 Redo Log 重放已提交事务,通过 Undo Log 回滚未提交事务。
执行流程图
客户端发送 UPDATE 语句
→ Server 层:SQL 解析与预处理
→ 查询优化器生成执行计划
→ 执行器调用 InnoDB 引擎
→ InnoDB:启动事务 + 获取行锁(X 锁 / 间隙锁)
→ 定位目标行(主键索引扫描)
→ 修改 Buffer Pool 中的数据页
→ 写入 Redo Log(Prepare 状态)
→ 写入 Undo Log(用于 MVCC 与回滚)
→ Server 层写入 Binlog
→ InnoDB 提交事务(Commit 状态)
→ 释放锁,返回成功
→ 后台线程异步刷脏页至磁盘
核心日志对比:Redo Log vs Undo Log vs Binlog
| 特性 | Redo Log | Undo Log | Binlog |
|---|---|---|---|
| 所属层级 | InnoDB 存储引擎 | InnoDB 存储引擎 | MySQL Server 层 |
| 记录内容 | 物理日志(页级修改) | 逻辑日志(修改前的旧值) | 逻辑日志(SQL 语句或行变更) |
| 主要用途 | 崩溃恢复(保证持久性) | 事务回滚 + MVCC 快照读 | 主从复制 + 时间点恢复 |
| 是否循环写入 | 是(固定大小,循环覆盖) | 否(随事务增长,由 Purge 清理) | 否(按文件轮转) |
| 是否受 2PC 控制 | 是(Prepare/Commit 阶段) | 是(作为 Redo 的一部分) | 是(2PC 中间环节) |
七、面试题
**面试题一:
MySQL 的 UPDATE 语句在什么情况下会加间隙锁?**
考察点:对 InnoDB 锁机制与隔离级别的理解。
答案:在 REPEATABLE READ 隔离级别下,若 WHERE 条件使用非唯一索引或无索引,为防止幻读会加间隙锁。
**面试题二:
为什么需要两阶段提交?如果只写 Redo Log 不写 Binlog 会有什么问题?**
考察点:对数据一致性与高可用架构的理解。
答案:会导致主从数据不一致;崩溃恢复后主库有数据,但从库因缺少 Binlog 无法同步。
**面试题三:
Undo Log 存储在哪里?它会被回收吗?**
考察点:对 MVCC 生命周期与存储管理的理解。
答案:早期版本存于共享表空间,MySQL 5.6+ 可配置为独立 Undo 表空间;由后台 Purge 线程在无活跃事务引用时回收。
**面试题四:
Change Buffer 对 UPDATE 有什么影响?**
考察点:对 I/O 优化机制的理解。
答案:仅加速非唯一二级索引的更新;主键或唯一索引更新不受益于 Change Buffer。









