ACID · 隔离级别 · 锁机制 · MVCC · 日志系统 · 两阶段提交
事务开启
生成唯一XID
决定并发控制策略
读写数据页
记录旧值
记录新值
可见性判断
两阶段提交 · 释放锁 · 清理Undo
数据写入磁盘
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 锁策略 |
|---|---|---|---|---|
| READ UNCOMMITTED | ✅ 可能 | ✅ 可能 | ✅ 可能 | 无锁,性能最高,风险最大 |
| READ COMMITTED | ❌ 不可能 | ✅ 可能 | ✅ 可能 | 行级锁,Oracle默认级别 |
| REPEATABLE READ | ❌ 不可能 | ❌ 不可能 | ❌ 可能(普通查询) ❌ 不可能(加锁读) |
Gap锁,MySQL默认级别 |
| SERIALIZABLE | ❌ 不可能 | ❌ 不可能 | ❌ 不可能 | 表级锁,性能最低,串行执行 |
事务A读取到事务B未提交的修改,事务B回滚后数据不存在
事务A在同一次读取中,两次得到不同的值(因为事务B修改并提交了)
事务A按条件查询,事务B插入了新行并提交,导致事务A第二次查询多了行
快照读用 MVCC
当前读用行锁+Gap锁
每次 SELECT 生成新快照
只读取已提交的事务
所有读加锁
强制串行
按维度划分
| S | X | |
|---|---|---|
| S 兼容 | ✅ 兼容 | ❌ 冲突 |
| X 冲突 | ❌ 冲突 | ❌ 冲突 |
SELECT ... FOR UPDATE
INSERT / UPDATE / DELETE
普通 SELECT(不带 FOR UPDATE)
利用 MVCC 读取历史版本
每行数据存储多个版本,事务读取时根据 Read View(读取视图) 判断哪个版本可见,从而实现读不加锁、读写不冲突。
修改事务ID
指向Undo链
回滚段ID
遍历版本链,找到第一个 trx_id < min_trx_id 或已提交的版本
每个 SELECT 语句开始时,都会重新生成一个 Read View。所以能看到其他事务已提交的修改。
事务中第一次 SELECT 时生成 Read View,之后复用同一个快照。整个事务期间读到的数据一致,不会看到新插入的行(解决幻读)。
数据页在内存中
记录变更(MTR)
记录逆向操作
刷 Redo → 返回成功
脏页异步落盘
标记为 prepared 状态
binlog 刷盘完成
Redo Log 写 commit 标记
数据持久化成功
若 Prepare 成功,但 binlog 写失败 → 回滚 InnoDB
若 binlog 写成功,但 Redo commit 失败 → 重试 commit(MySQL 内部处理)
⚠️ 若 binlog 写入后崩溃,MySQL 认为事务已提交(binlog 已持久化)
MySQL 重启后读取 ib_logfile 中的 LSN(Log Sequence Number)检查点,确定已刷盘的最大位置
扫描 Redo Log,将 checkpoint 之后所有已提交但未刷盘的事务重新执行,使数据页恢复到最新状态
对于 prepare 但未 commit 的事务,根据 Undo Log 执行回滚,释放所有锁
Purge 线程清理不再需要的 Undo 页面,释放空间
解析 → 优化 → 执行计划
行锁 / Gap锁 / IX锁
记录修改前的镜像
更新数据页(内存)
记录修改内容(MTR)
Redo 刷盘 → 释放锁
trx_id 唯一标识
(快照读时)
判断版本可见性
标记为脏页
形成版本链
(Server层两阶段)
Checkpoint 机制
事务开始与结束
靠 Undo Log 回滚
靠锁+约束保证
靠锁+MVCC
靠 Redo Log 刷盘
原子性+MVCC历史版本
持久性+崩溃恢复
并发控制+隔离级别
读不阻塞写+版本链
事务 = BEGIN → 锁 + Undo(原子性) + Redo(持久性) + 隔离级别(并发控制)→ COMMIT
Read View 由 隔离级别 决定生成时机
版本链 由 Undo Log + roll_ptr 构成
两阶段提交 保证 Redo + binlog 一致性