本文完整梳理一条 UPDATE 在 MySQL 中的执行链路(连接池 → Server 层 → InnoDB 内存 → Undo / Redo / Binlog → 异步落盘),以及 MySQL 与 InnoDB 的演进历程。理解这条链路,是回答两个问题的基础:事务提交后数据为什么不会丢?以及崩溃恢复依赖什么机制?
一、连接池让高并发下不能每次都建连
任何 SQL 的第一步都是建立连接。连接是稀缺且昂贵的资源,高并发下让业务线程各自开关连接会造成巨大开销。本节说明连接池为何是必选项。
Java 应用通过 JDBC 驱动把 SQL 发往 MySQL。一个运行在 Tomcat 中的 Web 系统,多个线程并发处理请求;若每个线程都走"建连接 → 执行 → 断连接",高并发下连接的频繁创建与销毁会让握手与鉴权开销被显著放大。
MySQL 与应用两侧都依赖连接池:预先建立一组连接,线程按需取用、用完归还。它带来三点收益:连接复用省去反复握手;连接数可控,避免拖垮数据库资源;管理简化,无需在业务代码里手动开关连接。常见的服务端连接池配置集中在 max_connections、wait_timeout、interactive_timeout,应用侧则依赖 HikariCP、Druid 等实现。

连接就绪后,请求进入 Server 层,看它如何消化这条 SQL。
二、Server 层中SQL 如何被解析与调度
连接建立后,服务线程接管请求。本层不碰数据文件,只把 SQL 翻译成执行方案交给引擎。
一条网络请求到达 MySQL 后,处理链路固定:
概括为:接口接收 →解析器解析→ 优化器选路 → 执行器调度 → 引擎落地。

一点历史细节:MySQL 5.7 及之前存在查询缓存(Query Cache),以表为单位缓存 SELECT 结果,但任意对该表的写(包括 UPDATE)都会让整张表的缓存失效,写多场景下反而拖累性能,因此默认关闭;MySQL 8.0 已彻底移除该功能。
计划定下后,读写交给存储引擎,InnoDB 的内存结构自此介入。
三、Buffer Pool是InnoDB 的内存心脏
InnoDB 读写多先发生在内存,Buffer Pool 是其性能核心。正因内存易失,才引出 Undo、Redo 与刷盘机制。本节先讲基础,再补 Change Buffer、Double Write 与三条链表。
InnoDB 将大量数据页缓存在内存的 Buffer Pool 中,以减少直接读磁盘的次数。
以如下语句为例:
— 场景:将 id=1 这行用户的 name 改为 xxx
UPDATE users SET name = 'xxx' WHERE id = 1;
InnoDB 依次做三件事:检查 id=1 所在数据页是否在 Buffer Pool,不在则从磁盘加载;对该行加独占锁,防止并发修改;在内存中把 name 改为 xxx。
此时内存中的数据已与磁盘不一致,称为"脏页"。Buffer Pool 用 LRU 算法管理数据页,保留热点数据在内存。专用数据库服务器建议将 innodb_buffer_pool_size 设为物理内存的 60%~75%,缓存命中率直接决定查询性能。
Change Buffer:非唯一索引的更新优化。 对于非唯一二级索引的改动,如果目标数据页不在 Buffer Pool,InnoDB 不会立即把页读入内存,而是先把变更记入 Change Buffer,等后续该页被读入时再合并(merge)。唯一索引因插入时必须立即判重,无法使用 Change Buffer。其行为由 innodb_change_buffering 控制(默认 all,即增删改都缓冲)。
脏页刷盘时机与 Double Write Buffer。 脏页由后台 IO 线程异步刷回磁盘,触发条件包括:Buffer Pool 空间不足、Redo Log 写满需要推进 checkpoint、后台定期刷写。写脏页时,InnoDB 先顺序写入 Double Write Buffer(内存 2MB 加 ibdata 中连续 128 页),再写真正的数据文件,用以防范"部分写失效"(16KB 页写到一半宕机导致页损坏);崩溃恢复时用副本修复。默认 innodb_doublewrite = ON。
Buffer Pool 的三条链表。 Buffer Pool 内部用三条链表管理页:
- free list:管理尚未使用的空闲页,分配新页时从这里取;
- LRU list:缓存页按改进 LRU 组织——新读入的页插入 LRU 中点(old 区)而非队首,避免全表扫描把热点页冲掉;old 区比例由 innodb_old_blocks_pct(默认 37%)控制,页在 old 区停留超过 innodb_old_blocks_time 才晋升 young 区;
- flush list:脏页按 oldest_modification LSN 升序排列,后台线程依此顺序刷盘。
内存改完但仍易失,宕机即丢,于是需要 Undo 留退路、Redo 保安全,先看 Undo Log。
四、回滚的Undo Log日志
改内存前须留退路。事务原子性与并发快照读都靠 Undo Log 支撑。本节说明其作用。
在修改内存之前,InnoDB 先把修改前的旧值写入 Undo Log。
它的作用直接:事务回滚时,依靠 Undo Log 将数据逆向还原到事务开始前的状态,这是事务原子性的底层支撑。此外,MVCC 多版本快照也依赖 Undo Log 存储历史版本,使读请求能拿到一致的快照而不被写阻塞。Undo Log 分两类:insert undo(插入产生的,事务提交后可直接丢弃)与 update undo(更新/删除产生的,需保留供 MVCC 历史版本,由 purge 线程在不再被引用后清理)。
Undo 解决回滚与快照读,但没解决崩溃不丢。真正兜底的是 Redo Log。
五、MVCC 与事务隔离级别
Undo Log 支撑 MVCC。MVCC 靠行版本链与 ReadView 实现读写不阻塞,隔离级别决定可见哪些版本。本节再补锁机制,说明并发修改如何被约束。
InnoDB 每行记录带有隐藏列:DB_TRX_ID(最后一次修改该行的事务 ID)与 DB_ROLL_PTR(回滚指针,指向 Undo Log 中的历史版本)。事务发起读时,InnoDB 生成 ReadView,依据各版本的事务 ID 判断可见性:只有"已提交且不在当前事务活跃集合"的版本才可见,从而拿到一致性快照。
四种标准隔离级别的差异如下:
| READ UNCOMMITTED | 无快照 | 脏读(读到未提交) | 直接读最新 |
| READ COMMITTED | 语句级 | 不可重复读 | 每条语句新建 ReadView |
| REPEATABLE READ(默认) | 事务级 | 幻读(靠 Gap 锁缓解) | 事务首次读建 ReadView,全程不变 |
| SERIALIZABLE | 串行 | 无 | 读加共享锁,等价于串行 |
锁:行锁、间隙锁与 Next-Key Lock。 MVCC 解决读写不阻塞,写写冲突靠锁约束:
- Record Lock(记录锁):锁住索引上的具体记录;
- Gap Lock(间隙锁):锁住记录之间的间隙,防止其他事务插入,用于解决幻读;
- Next-Key Lock:Record Lock + Gap Lock 的组合,锁定记录及前面间隙(左开右闭),是 RR 级别下的默认行锁算法;
- 意向锁 IS / IX:表级锁,协调表锁与行锁,避免逐行检查。
RC 级别下通常只使用 Record Lock(唯一索引等值查询除外),RR 级别下使用 Next-Key Lock。隔离级别决定可见哪些版本,但这些版本仍在内存中,宕机即丢,恢复靠Redo Log。
六、Redo Log:崩溃也不丢的底气
内存改完未落盘,宕机即丢。Redo Log 用顺序写 + 崩溃重放换取提交即可不丢。本节讲原理、WAL/ARIES、文件结构与 LSN/Checkpoint。
Redo Log 是一份物理日志,记录"对哪个数据页的哪条记录做了什么修改",有两个作用:崩溃后重放 Redo Log,可恢复到最后一次提交的状态;把随机写磁盘转为顺序写日志,提升写入吞吐。
WAL 与 ARIES 原则。 Redo Log 的理论基础是 WAL(Write-Ahead Logging,先写日志):事务提交时,其对应的 Redo 必须先落盘(force log at commit),数据页可延后刷盘。这样用顺序日志写替代随机页写,崩溃后靠重放恢复。InnoDB 的实现思想源自 IBM 的 ARIES 算法,核心三条:WAL(先写日志)、Repeat History(重放时连未提交事务的操作一起重放)、Undo 用日志记录的逆操作回滚。工程上 InnoDB 采用 steal(脏页可提前刷盘)+ no-force(事务提交不必立即刷数据页),持久性完全由 Redo 保证。
文件结构与循环写。 Redo Log 由一组文件(如 ib_logfile0、ib_logfile1)组成,由 innodb_log_files_in_group 与 innodb_log_file_size 决定大小,环状循环写入。文件上有 write pos(当前写入位置)和 checkpoint(已刷盘脏页对应的重放点),二者之间的区域是可用空间;checkpoint 推进表示对应脏页已落盘,该段 Redo 可被覆盖。
LSN 与 Checkpoint。 LSN(Log Sequence Number)是单调递增的日志序号,数据页、Redo 记录、Checkpoint 都带 LSN。Checkpoint 记录"已刷盘脏页对应的最老 LSN",崩溃恢复时只需重放该 LSN 之后的 Redo。运行期采用 fuzzy checkpoint(master thread、flush list、脏页过多等触发),不阻塞写入;恢复阶段比较页 LSN 与 Redo LSN,仅重放页 LSN 之后的记录。
刷盘策略。 修改发生时先写入内存的 Redo Log Buffer(默认 8MB,由 innodb_log_buffer_size 控制),事务提交时刷盘。刷盘策略由 innodb_flush_log_at_trx_commit 控制:
| 0 | 提交时仅写入 OS 缓存,不立即刷盘 | 性能最好,崩溃可能丢数据 |
| 1(默认) | 提交时立即刷盘并等待 IO 完成 | 最安全,性能略有损耗 |
| 2 | 提交时写入 OS 缓存,由后台异步刷盘 | 性能与保护折中,仍有风险 |
线上核心业务建议设为 1,提交即落盘,严格保证数据不丢。
崩溃恢复时如何重放。 重启后 InnoDB 进入恢复阶段:先用 Redo Log 把记录重放到内存(含已提交和崩溃瞬间未完成的操作,即 Repeat History),再对 Redo 中没有 commit 标记的事务,用 Undo Log 回滚。两阶段提交保证 Redo 与 Binlog 一致:提交时先写 Redo(prepare,无 commit 标记)、再写 Binlog、最后在 Redo 写 commit 标记;恢复时若 Binlog 已完整写入则补提交,否则回滚。
Redo 保引擎层不丢,但主从复制与恢复依赖 Server 层的 Binlog。
七、Binlog:Server 层的归档日志
Binlog 是 Server 层逻辑日志,与引擎无关,可跨引擎用于复制与恢复。
Binlog 记录"对 users 表 id=1 那行做了更新,更新后的值是什么",归属 Server 层。Binlog 刷盘策略由 sync_binlog 控制,默认 0(先进入 OS 缓存,宕机可能丢),设为 1 则提交时强制落盘。
Binlog 有三种格式:
| STATEMENT | 原始 SQL 语句 | 日志量小 | now()/uuid()/limit 等不确定函数致主从不一致 |
| ROW(默认) | 每行前后像 | 主从绝对一致,最安全 | 日志量大 |
| MIXED | 自动切换 | 兼顾空间与正确 | 行为依赖优化器判断 |
两阶段提交:让 Redo 与 Binlog 保持一致。 提交事务、Binlog 落盘后,InnoDB 还会做关键一步:将本次更新的 Binlog 文件名与位置写入 Redo Log,并在 Redo Log 中写入 commit 标记。只有 Redo Log 中出现了这个标记,事务才算真正提交成功。这一步的意义在于:保证 Redo Log 与 Binlog 两侧都完整记录了这次更新,避免出现"一边有、一边没有"的不一致。在内部实现上,这是一个 XA 事务,由 Server 层统一协调 Redo 与 Binlog 的提交顺序。
最后,后台 IO 线程会在之后的某个时机,将 Buffer Pool 中的脏页异步刷回磁盘。即便在刷盘前崩溃,重启后也会依据 Redo Log 恢复内存中的修改,待时机成熟再刷盘。
Redo 与 Binlog 对齐、脏页落盘,链路闭合。
为啥采用2PC? 不用 2PC,两种错误执行顺序都会出数据错乱
方案 A:先写 Redo 提交,再写 Binlog
- 时序:更新内存 → Redo 刷盘标记 COMMIT → 宕机 → Binlog 还没写
- 本地 InnoDB:重启回放Redo,这条更新存在;
- Binlog:无这条记录,从库 / 备份不会同步该更新;
结果:主库多数据,从库少数据,主从不一致。
方案 B:先写 Binlog,再写 Redo 提交
- 时序:更新内存 → Binlog 完整落盘 → 宕机 → Redo 没标记提交
- Binlog 已持久化,从库已经同步执行更新;
- InnoDB 重启后,Redo 无提交标记,直接回滚事务;
结果:主库少数据,从库多数据,主从不一致。
关键矛盾
两个 IO 操作中间随时宕机,必然出现一份日志有事务、另一份没有。
业务、主从、定时备份全部错乱,数据完全不可信。
单纯串行顺序无法解决 “中间宕机” 的原子性问题,必须引入协调机制,也就是两阶段提交。
为啥 Binlog 在 server 层?
- Binlog 是全引擎通用、实例全局的复制 / 备份日志,MyISAM 等无事务引擎也需要;
- Binlog 记录 SQL 逻辑变更,由Server 层 SQL 执行器统一生成; 主从复制、时间点恢复属于 MySQL 顶层服务能力,不属于单个存储引擎;
- 2PC 协议需要Server 作为协调者持有 Binlog,才能协调 InnoDB 的 Redo 保证一致性;
- 分层架构职责解耦,通用能力放Server,引擎专属事务 / 物理日志下沉引擎。
八、一条 UPDATE 的完整时间线
把前面各组件按时间顺序排成一张表,建立端到端认知,共九个步骤。
| 1 | JDBC + 连接池 | 应用发送 SQL,获取连接 |
| 2 | 网络 + 线程 | MySQL 分配线程,读取并解析 SQL |
| 3 | Server 层 | 接口接收 → 解析器解析 → 优化器选路 → 执行器调度 |
| 4 | InnoDB | 加载数据页进 Buffer Pool,加独占锁 |
| 5 | Undo Log | 记录修改前旧值 |
| 6 | Buffer Pool | 内存中改为新值(产生脏页) |
| 7 | Redo Log Buffer | 记录物理修改 |
| 8 | 提交事务 | Redo 刷盘 → Binlog 刷盘 → Redo 写 commit 标记 |
| 9 | 后台 IO 线程 | 后续某时机将脏页刷回磁盘 |

这张表是全文骨架,跑稳机制还需正确配置参数。
九、落地到配置:关键参数
原理落到参数。下面汇总影响性能与安全的关键参数及取舍。
| innodb_buffer_pool_size | 物理内存 60%~75% | 缓存命中率直接决定性能 |
| innodb_log_file_size | 512M~1G(权衡恢复时长) | 越大写吞吐越高,但崩溃恢复越慢 |
| innodb_flush_log_at_trx_commit | 1 | 事务提交 Redo 即落盘,不丢数据 |
| sync_binlog | 1 | 事务提交 Binlog 即落盘,主从一致 |
| innodb_doublewrite | ON | 双写防部分写失效,一般不关 |
| innodb_change_buffering | all | 非唯一索引改动进 Change Buffer |
| innodb_io_capacity | 按磁盘(SSD 2000~4000) | 后台刷脏页的 IO 能力上限 |
核心业务务必把 innodb_flush_log_at_trx_commit 与 sync_binlog 都设为 1,以换取严格不丢;吞吐敏感且可容忍少量丢失的场景才考虑放宽。
十、实现历程:MySQL 与 InnoDB 的演进
理解底层原理,也需知道这套架构从何而来。MySQL 自设计之初就把 Server 层与存储引擎解耦(插件化引擎),Server 负责 SQL 解析、优化、日志,引擎负责数据存储,InnoDB 正是这一架构下最关键的引擎。
关键节点:
- 1995 年:MySQL 由 Michael “Monty” Widenius 等人创立(MySQL AB,瑞典),以 C/C++ 编写,最初作为 mSQL 的替代。
- 早期默认引擎为 MyISAM:表级锁、非事务、无崩溃恢复,读多写少场景表现好但有明显短板。
- InnoDB 由 Heikki Tuuri 创立的 Innobase Oy(芬兰)开发,支持事务、行锁、MVCC、外键;2000 年前后作为可选引擎集成进 MySQL,5.0 起趋于成熟。
- 2005 年 Oracle 收购 Innobase;2008 年 Sun 收购 MySQL AB;2010 年 Oracle 收购 Sun,MySQL 归 Oracle 所有。
- 2010 年 MySQL 5.5 起,InnoDB 正式取代 MyISAM 成为默认存储引擎。
- 社区分支:MariaDB(由 Monty 主导的社区分支)、Percona Server 等,在 InnoDB 基础上各自演进。
主要版本里程碑:
| 3.23 / 4.0 | 2000 前后 | InnoDB 作为可选引擎集成(事务 / 行锁 / MVCC) |
| 5.0 | 2003–2005 | InnoDB 成熟,引入视图、存储过程、触发器 |
| 5.1 | 2008 | 分区表、事件调度、InnoDB 插件 |
| 5.5 | 2010 | InnoDB 成默认引擎;半同步复制;performance_schema |
| 5.6 | 2013 | GTID、并行复制、InnoDB 全文索引、online DDL |
| 5.7 | 2015 | JSON、生成列、多源复制、sys schema、查询缓存默认关闭 |
| 8.0 | 2018 | CTE/窗口函数、原子 DDL、不可见索引、移除查询缓存、数据字典入 InnoDB |
从 MyISAM 到 InnoDB 的更替,本质上是"事务与崩溃安全"成为刚需的结果;而 WAL、MVCC、两阶段提交这些原理,正是 InnoDB 能扛住核心业务的根基。
小结
一条 UPDATE 从应用发出,经连接池与 Server 层进入 InnoDB,先在 Buffer Pool 改内存、写 Undo 留退路、记 Redo 保崩溃安全,再经两阶段提交让 Binlog 与 Redo 对齐,最后由后台线程异步刷脏页落盘。WAL/ARIES 决定了"先写日志",LSN/Checkpoint 决定了"恢复从哪里开始",MVCC 与锁决定了并发下的可见性与正确性。
这套机制并非一蹴而就,而是随 MySQL/InnoDB 从 MyISAM 到默认引擎的演进逐步定型,也是索引调优、参数配置与故障排查的共同基础。
文章结束,喜欢就给个一键三连吧,你的肯定是我最大的动力,点赞上一千我就是脑瘫也出下章。
网硕互联帮助中心





评论前必须登录!
注册