MySQL 事务的实现原理详解
重点围绕 InnoDB 存储引擎,因为真正支持事务的是 InnoDB。
一、事务与 ACID 简单回顾
事务(Transaction):一组要么全部成功、要么全部失败的 SQL 操作,是数据库并发控制的基本单位。
ACID 四个特性:
-
原子性(Atomicity)
- 要么全部执行,要么全部回滚。
- 失败时,数据库能恢复到事务开始前的状态。
-
一致性(Consistency)
- 事务前后,数据库从一个“合法状态”到另一个“合法状态”。
- 约束(主键、唯一约束、外键等)不会被破坏。
-
隔离性(Isolation)
- 多个事务并发执行时,相互之间的可见性如何控制。
- 对应四种隔离级别:RU、RC、RR、Serializable。
-
持久性(Durability)
- 提交后的修改即使宕机也不会丢失。
- 靠的是各种日志和刷盘机制。
二、InnoDB 事务的大框架
InnoDB 为了实现 ACID,核心用到了这几样东西:
- Redo Log(重做日志) —— 保证持久性
- Undo Log(回滚日志) —— 保证原子性 + MVCC 的基础
- 锁(Lock)系统 —— 行级锁实现隔离性的一部分
- MVCC(多版本并发控制) —— 实现 RC / RR 隔离级别下的“快照读”
- 两阶段提交(Redo Log + Binlog) —— 保证崩溃恢复 & 主从复制的一致性
整体可以理解为:
“写操作先记日志(WAL 思想),通过日志 + 版本控制 + 锁来维持 ACID。”
三、原子性:Undo Log 如何工作
原子性 = 要么全做,要么全不做。InnoDB 通过 Undo Log 实现“回滚能力”。
1. Undo Log 是什么
- Undo Log 记录的是:如何把新数据恢复成旧数据。
- 例如:
UPDATE t SET age = 18 WHERE id = 1;- Undo 里会记录:原来的
age是多少。
- Undo 里会记录:原来的
2. Undo Log 的作用
-
事务回滚
- 事务失败或主动
ROLLBACK时,InnoDB 按 Undo Log 逆向操作,把数据恢复成修改前的版本。
- 事务失败或主动
-
MVCC 的版本链基础
- 每行记录除了当前值外,还通过 Undo Log 把历史版本串成一条“版本链”。
- 这样“快照读”就可以读到旧版本,做到“读到事务开始时的视图”。
3. Undo Log 的类型
-
Insert Undo Log:对
INSERT操作产生。- 事务未提交前,需要它来回滚“插入”。
- 提交后这类 Undo 其实可以被丢弃(因为没人再需要看到“插入之前”的“没有这条记录”的版本)。
-
Update Undo Log:包括
UPDATE、DELETE。- 既用于回滚,也用于给其他事务提供历史版本(MVCC)。
- 提交后不能立刻删除,要等没有事务再需要这些旧版本。
四、持久性:Redo Log 与 WAL 思想
持久性 = 提交之后不能丢。
1. Redo Log 是什么
- Redo 记录的是:对“数据页”的物理修改,类似于“操作步骤”。
- 比如:修改了哪一个表空间、哪一页、哪个偏移量、改成什么值。
2. 为什么需要 Redo Log
直接把页面写回磁盘成本很高:
- 随机写多、IO 慢;
- 如果宕机在一半,页面处于中间状态。
所以 InnoDB 采用 WAL(Write-Ahead Logging):
- 先写 Redo Log(顺序写) 到日志文件/缓冲。
- 再异步把数据页刷回磁盘。
只要 Redo Log 写成功,就认为事务“持久化”了。
3. 提交时发生了什么
一个写事务大概流程:
- 修改内存中的 Buffer Pool 页(脏页)。
- 生成 Redo Log 写入 Redo Log Buffer。
- 事务
COMMIT时,必须保证相关 Redo 至少刷新到磁盘(或者根据innodb_flush_log_at_trx_commit的策略)。 - 真正的数据页晚点刷也没关系,宕机时可以靠 Redo Log“重做”回来。
4. 崩溃恢复时 Redo 的作用
- 启动时:扫描 Redo Log,从最后一次一致性检查点开始重做,确保所有已经提交的事务都物理落盘。
五、隔离性:锁 + MVCC 的组合拳
隔离性主要通过两部分实现:
- 锁机制(Locking) —— 控制对“同一行或范围”的并发修改
- MVCC(多版本并发控制) —— 让读操作尽量不用加锁,直接读“快照”
1. 锁的种类(InnoDB 行级锁)
常见锁:
- 记录锁(Record Lock):锁住某一条记录。
- 间隙锁(Gap Lock):锁住两个值之间的“间隙”,不锁已有记录。
- Next-Key Lock:记录锁 + 间隙锁,锁住“某条记录以及它前面的间隙”,用于防止幻读。
- 意向锁(Intention Lock):表级锁,用来快速判断“某个表上是否有人加过行锁”。
思路:在高并发下,通过尽可能细粒度的行锁 + 间隙锁控制写冲突。
2. MVCC 的基本结构
InnoDB 每行记录内部有几个隐藏字段:
trx_id:最近一次修改该行的事务 id。roll_pointer:指向 Undo Log 中该行的前一个版本。
于是:
- 当前行 = 最新版本。
- 沿着
roll_pointer可以找到历史版本。 - 不同事务按照自己的“快照规则”,去选择某个版本来读。
这样:
- 快照读(普通 SELECT):不加锁,通过版本链读取旧数据版本。
- 当前读(SELECT … FOR UPDATE / UPDATE / DELETE):要加锁,读的是当前最新版本。
六、四种隔离级别是如何实现的
MySQL 提供 4 种隔离级别:
- 读未提交(READ UNCOMMITTED,RU)
- 读已提交(READ COMMITTED,RC)
- 可重复读(REPEATABLE READ,RR)—— InnoDB 默认
- 串行化(SERIALIZABLE)
对应现象:脏读、不可重复读、幻读。
1. READ UNCOMMITTED
- 读操作可以看到“未提交事务”的修改。
- 实现上基本不使用 MVCC 快照,直接读最新版本。
- 问题多,一般不用。
2. READ COMMITTED
- 每条 SQL 开始时生成一个新的“快照”。
- 同一个事务内,不同的 SELECT 可能读到不同版本的数据(不可重复读)。
- 实现上:
- 通过 MVCC,按照当前语句执行时的活跃事务列表过滤版本。
3. REPEATABLE READ(InnoDB 默认)
- 在事务开始时生成一致性视图(快照)。
- 整个事务生命周期内,快照不变:
- 多次 SELECT 结果一样(可重复读)。
- 幻读问题:
- 对于快照读,通过 MVCC 的版本规则避免看到其他事务新插入的数据。
- 对于当前读(加锁的范围查询),通过 Next-Key Lock(记录锁 + 间隙锁)防止“幻影记录”插入。
4. SERIALIZABLE
- 所有读都是加锁读,相当于“串行执行”。
- 并发性最差,但隔离性最高。
七、一致性:规则 + 日志 + 约束
一致性是结果上的“正确性”,主要来源于:
- 应用层逻辑:业务本身必须写对。
- 数据库约束:主键、外键、唯一约束、触发器等。
- 事务隔离:防止并发行为破坏规则。
- 日志与恢复机制:崩溃后回放 Redo / 回滚 Undo,确保不会得到“半条更新”的中间状态。
简化理解:
原子性 + 隔离性 + 持久性 + 约束 = 一致性。
八、Redo Log 与 Binlog 的两阶段提交
为了保证 MySQL Server 层的 Binlog 和 InnoDB 的 Redo Log 一致,InnoDB 采用“两阶段提交”:
-
阶段一:准备阶段(Prepare)
- 写入 Redo Log(状态标记为
prepare)。
- 写入 Redo Log(状态标记为
-
阶段二:提交阶段(Commit)
- 写 Binlog。
- 把 Redo Log 状态改为
commit。
崩溃恢复时:
- 看到 Redo Log 处于
prepare状态,判断 Binlog 是否完整,决定是提交还是回滚。
这样就保证:
- 不会出现 binlog 里有记录但 InnoDB 没有提交,或者反过来的情况。
- 主从复制、崩溃恢复结果一致。
九、锁与两阶段锁协议
InnoDB 行锁遵循 两阶段锁协议(2PL):
- 在执行 SQL 过程中,不断 加锁,直到事务结束。
- 事务提交或回滚时,一次性释放所有锁。
这会导致一些现象:
- 一条后面的 SQL 所加的锁,可能会让前面已经执行的 SQL 也被锁住资源(因为都属于一个事务)。
- 死锁:多个事务互相等待对方的锁。
- InnoDB 会检测死锁(构建 wait-for 图),选出代价最小的事务回滚。
十、崩溃恢复的完整过程(简化版)
假设实例宕机,再启动:
- 加载数据字典 & 基本信息。
- 扫描 Redo Log,从 checkpoint 开始:
- 对于已经
commit的事务但数据没落盘的,进行 重做。
- 对于已经
- 回滚未提交事务:
- 利用 Undo Log,把它们做过的修改挨个撤销。
最终:
- 所有已提交的事务都“看起来像是完全执行完了”;
- 所有未提交的事务“像是从未发生过”。
- 原子性 + 持久性 得到了保证。
十一、整体总结(帮助你脑中形成一张图)
可以把 MySQL(InnoDB)的事务实现想象成一套“流水线”:
- 写前日志(WAL)
- 修改先写 Redo Log,再写数据页,保证崩溃可恢复(持久性)。
- 回滚日志(Undo Log)
- 记录旧版本,支持回滚(原子性)+ 快照读(MVCC)。
- MVCC + 锁
- 快照读用 MVCC,当前读用行锁 + 间隙锁,共同实现隔离性。
- 两阶段提交
- Redo Log + Binlog 的两阶段提交,确保存储引擎和 Server 层的一致性。
- 约束 + 规则
- 加上主键、唯一约束、外键以及业务逻辑,保证数据语义上的一致性。
掌握上面这些,你就已经从“会用事务”升级到“能从实现原理思考问题”的阶段了。
1933

被折叠的 条评论
为什么被折叠?



