我是小耶,干运营半路出家的野生 DBA——写功课只是为了我踩过的坑,你们别再踩了!
面试时经常被问:执行 UPDATE users SET name = '小耶' WHERE id = 1;,MySQL 内部到底发生了什么?今天我把这个流程完整走一遍,顺便解释几个关键参数。
第一阶段:SQL 层(Server 层)
- 连接器:客户端与 MySQL 建立连接,验证用户名密码,检查是否对该表有 UPDATE 权限。
- 分析器:解析 SQL,检查语法、表是否存在、字段是否存在。没有问题就生成解析树。
- 优化器:决定使用哪个索引。对于
WHERE id = 1,如果id是主键,优化器会选主键索引。还会确定执行计划:是直接更新,还是先读再写。 - 执行器:调用存储引擎接口,开始执行。
第二阶段:InnoDB 引擎内部
- 从磁盘或 Buffer Pool 读取数据页:如果
id=1所在的数据页已经在内存中(Buffer Pool),直接使用;否则从磁盘读取并缓存到 Buffer Pool。 - 写入 Undo Log:记录修改前的旧值,存储于 undo 表空间。Undo Log 用于事务回滚和 MVCC(多版本并发控制)。
- 修改内存中的数据页:在 Buffer Pool 中直接修改
name字段为新值。此时数据尚未写入磁盘。 - 写入 Redo Log Buffer:记录物理变更(例如“在表 space 5,page 123,offset 456 处,将字段 name 从旧值改为新值”)。Redo Log 是循环写的物理日志,保证持久性。
- 写入 Binlog:记录逻辑变更(例如“将 users 表中 id=1 的 name 字段从旧值改为新值”)。Binlog 是追加写的逻辑日志,主要用于主从复制和时间点恢复。
- 提交事务(Commit):执行
COMMIT时,触发两阶段提交(2PC):- Prepare 阶段:Redo Log 写入 prepare 状态。
- 写 Binlog:将 Binlog 刷盘(根据
sync_binlog设置)。 - Commit 阶段:Redo Log 写入 commit 状态。此时事务持久化完成。
关键参数详解
innodb_flush_log_at_trx_commit:0:每秒刷一次 Redo Log,事务提交时不刷。性能最高,但可能丢失 1 秒数据。1:每次提交都刷 Redo Log 到磁盘。最安全(不会丢数据),但性能最低。2:每次提交只写 Redo Log 到操作系统缓存,每秒刷盘。性能折中,但 MySQL 崩溃不丢数据,操作系统崩溃可能丢 1 秒数据。
sync_binlog:0:Binlog 由操作系统决定何时刷盘。1:每次事务提交都刷 Binlog 到磁盘。最安全。N:每 N 个事务刷一次 Binlog。
生产环境推荐:innodb_flush_log_at_trx_commit=1 且 sync_binlog=1,保证 ACID 中的持久性(D)。虽然性能会降低 10-20%,但数据安全第一。
两阶段提交为什么要设计?
为了保证 Redo Log 和 Binlog 的逻辑一致。假设没有 2PC,在写入 Redo Log 之后、写入 Binlog 之前崩溃,主库重启后 Redo Log 会恢复数据,但从库没有 Binlog 导致主从不一致。2PC 通过 prepare 和 commit 两个状态,配合崩溃恢复机制,保证了两个日志的一致性。
崩溃恢复过程:
- 如果 Redo Log 是 prepare 状态,Binlog 完整(有该事务的 GTID),则提交。
- 如果 Redo Log 是 prepare 状态,Binlog 不完整,则回滚。
价值总结
理解 UPDATE 的完整生命周期,能帮你:
- 合理设置刷盘参数,平衡性能与安全
- 理解为什么突然断电后数据还能恢复
- 掌握主从复制的底层原理
- 在面试中讲出深度
小耶在手,SQL 不愁。
你的生产环境用了哪种刷盘设置?遇到过数据丢失吗?