MySQL 的事务实现主要依赖于存储引擎(默认使用 InnoDB),通过 日志机制、锁机制 和 多版本并发控制(MVCC) 来满足事务的 ACID 特性。以下是其核心实现原理:
一、ACID 特性的实现
1. 原子性(Atomicity)
- 目标:事务的所有操作要么全部完成,要么全部回滚。
- 实现:
- Undo Log(回滚日志):
- 记录事务修改前的数据快照(旧版本数据)。
- 若事务回滚或崩溃恢复时,通过 Undo Log 将数据恢复到事务前的状态。
- Undo Log 存储在 Undo Tablespace 中(默认位于系统表空间或独立文件)。
- 事务状态管理:
- 每个事务有唯一的事务 ID(
TRX_ID
),通过 InnoDB 的 事务系统 跟踪事务状态(活跃、提交、回滚)。
- 每个事务有唯一的事务 ID(
- Undo Log(回滚日志):
2. 持久性(Durability)
- 目标:事务提交后,修改永久保存,即使系统崩溃也不丢失。
- 实现:
- Redo Log(重做日志):
- 记录事务对数据页的物理修改(例如:“将 page 5 的 offset 100 处修改为 42”)。
- 事务提交时,Redo Log 会先刷盘(
fsync
),保证数据修改的持久性(即使宕机,恢复时也能重放日志)。
- Double Write Buffer:
- 防止数据页部分写入(partial page write)导致的损坏。在修改数据页前,先将页副本写入 Double Write Buffer,再写入实际位置。
- Redo Log(重做日志):
3. 隔离性(Isolation)
- 目标:并发事务之间互不干扰。
- 实现:
- 锁机制:
- 共享锁(S Lock):允许读,阻止其他事务加排他锁。
- 排他锁(X Lock):阻止其他事务加任何锁。
- 行级锁:InnoDB 默认对修改的行加锁(而非表锁),减少锁冲突。
- 间隙锁(Gap Lock) 和 Next-Key Lock:防止幻读(Phantom Read)。
- MVCC(多版本并发控制):
- 通过 Undo Log 维护数据的多个版本,实现非锁定读(Read Committed 和 Repeatable Read 隔离级别下)。
- 每个事务启动时生成一个 ReadView,决定可见哪些版本的数据:
TRX_ID
< 当前活跃事务的最小 ID → 可见。TRX_ID
> 当前事务 ID → 不可见。TRX_ID
在活跃事务列表中 → 不可见。
- 锁机制:
4. 一致性(Consistency)
- 目标:事务执行后,数据库从一个有效状态转换到另一个有效状态。
- 实现:
- 通过原子性、隔离性、持久性共同保证。
- 数据库约束(如主键、外键、唯一约束、CHECK 约束)确保数据逻辑一致。
二、事务执行流程(以 InnoDB 为例)
1. 事务开启
- 显式:
BEGIN
或START TRANSACTION
。 - 隐式:执行第一条 SQL 时自动开启。
2. 数据修改(以 UPDATE 为例)
- 加锁:
- 对目标行加排他锁(X Lock),阻止其他事务修改。
- 写 Undo Log:
- 记录修改前的数据到 Undo Log,用于回滚和 MVCC。
- 修改 Buffer Pool:
- 在内存中更新数据页(此时数据页变为“脏页”)。
- 写 Redo Log:
- 将修改记录到 Redo Log Buffer(事务提交时刷盘)。
3. 事务提交
- Redo Log 刷盘:
- 将 Redo Log Buffer 中的日志写入磁盘(
fsync
),确保持久性。
- 将 Redo Log Buffer 中的日志写入磁盘(
- 释放锁:
- 释放事务持有的行锁(间隙锁可能保留到事务结束)。
- 清理 Undo Log:
- 若事务已提交,Undo Log 可被后续事务覆盖(若其他事务不再需要旧版本数据)。
4. 事务回滚
- 逆向应用 Undo Log:
- 根据 Undo Log 将数据恢复到修改前的状态。
- 释放锁:
- 释放所有锁资源。
三、隔离级别与并发控制
MySQL 支持四种隔离级别(由低到高):
- 读未提交(Read Uncommitted):
- 可能读到未提交的数据(脏读),无锁或 MVCC 控制。
- 读已提交(Read Committed):
- 通过 MVCC 实现:每次查询生成新 ReadView,避免脏读。
- 可重复读(Repeatable Read):
- 默认隔离级别。事务开始时生成 ReadView,后续查询复用该视图,避免不可重复读。
- 通过 Next-Key Lock(行锁 + 间隙锁)防止幻读。
- 串行化(Serializable):
- 所有读操作加共享锁,写操作加排他锁,完全串行执行。
四、崩溃恢复
InnoDB 重启时通过以下步骤恢复事务:
- Redo Log 重放:
- 根据 Redo Log 恢复未刷盘的脏页(确保已提交事务的持久性)。
- Undo Log 回滚:
- 对未提交的事务(TRX_ID 在活跃事务列表中)应用 Undo Log 回滚。
五、关键机制对比
机制 | 作用 | 关联 ACID |
---|---|---|
Undo Log | 回滚事务、实现 MVCC | 原子性、隔离性 |
Redo Log | 保证数据修改的持久性 | 持久性 |
锁机制 | 控制并发访问,防止脏写、不可重复读 | 隔离性 |
MVCC | 实现非锁定读,避免读写冲突 | 隔离性 |
DoubleWrite | 防止数据页部分写入损坏 | 持久性 |
六、示例:MVCC 如何避免脏读
假设事务 A 更新一行数据:
-- 事务 A
BEGIN;
UPDATE users SET balance = 100 WHERE id = 1; -- 旧值 balance=50
- Undo Log 会记录
balance=50
。 - 事务 B 在 Read Committed 隔离级别下读取该行:
-- 事务 B BEGIN; SELECT balance FROM users WHERE id = 1; -- 返回 50(ReadView 看不到事务 A 的未提交修改)
- 事务 A 提交后,事务 B 再次查询会生成新 ReadView,看到
balance=100
。
总结
MySQL(InnoDB)通过 Redo Log 和 Undo Log 实现原子性与持久性,通过 锁机制 和 MVCC 实现隔离性,最终达成一致性。设计时需根据业务场景选择合适的隔离级别,并关注锁竞争与日志写入性能。