隔离级别与 MVCC
约 1038 字大约 3 分钟
布欧-Lewyon
2026-05-15
首页 › MySQL › 事务与锁(在新窗口打开) › 隔离级别与 MVCC
隔离级别控制并发事务之间的可见性。MySQL InnoDB 通过 MVCC(Multi-Version Concurrency Control) 在不加锁的情况下实现高并发读。
四种隔离级别
| 级别 | 脏读 | 不可重复读 | 幻读 | 说明 |
|---|---|---|---|---|
READ UNCOMMITTED | ✅ 可能 | ✅ 可能 | ✅ 可能 | 未提交读,极不推荐 |
READ COMMITTED | ❌ | ✅ 可能 | ✅ 可能 | Oracle/PostgreSQL 默认 |
REPEATABLE READ | ❌ | ❌ | ❌(InnoDB) | MySQL 默认 |
SERIALIZABLE | ❌ | ❌ | ❌ | 所有读自动加锁,性能低 |
-- 查看当前隔离级别
SELECT @@transaction_isolation;
-- MySQL 8.0 之前用: SELECT @@tx_isolation
-- 设置隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;脏读、不可重复读、幻读
脏读(Dirty Read)
读到其他事务未提交的数据。
-- 事务 A | 事务 B
-- |
SET SESSION TRANSACTION | SET SESSION TRANSACTION
ISOLATION LEVEL | ISOLATION LEVEL
READ UNCOMMITTED; | READ UNCOMMITTED;
START TRANSACTION; | START TRANSACTION;
|
UPDATE account |
SET balance = 0 |
WHERE name = '张三'; |
| SELECT balance FROM account
| WHERE name = '张三';
| -- 读到 0(脏数据!)
ROLLBACK; | -- A 回滚,张三余额恢复 1000
| -- B 的 0 是脏读不可重复读(Non-Repeatable Read)
同一事务内两次读取同一行,结果不同(因其他事务提交了修改)。
-- 事务 A | 事务 B
SET SESSION TRANSACTION |
ISOLATION LEVEL |
READ COMMITTED; |
START TRANSACTION; |
SELECT balance |
FROM account |
WHERE name = '张三'; |
-- 读: 1000 | UPDATE account
| SET balance = 800
| WHERE name = '张三';
| COMMIT;
SELECT balance |
FROM account |
WHERE name = '张三'; |
-- 读: 800(两次不一致) |幻读(Phantom Read)
同一事务内两次范围查询,返回的行数不同(因其他事务插入了新行)。
-- 事务 A | 事务 B
SET SESSION TRANSACTION |
ISOLATION LEVEL |
REPEATABLE READ; |
START TRANSACTION; |
SELECT * FROM account |
WHERE balance > 500; |
-- 返回 3 行 | INSERT INTO account
| VALUES (4, '王五', 600);
| COMMIT;
SELECT * FROM account |
WHERE balance > 500; |
-- RR 级别下 InnoDB 也返回 3 行(无幻读)
-- RC 级别下则返回 4 行(幻读)MySQL InnoDB 的
REPEATABLE READ通过 Next-Key Lock 防止了幻读,而标准 SQL 的定义中 RR 是允许幻读的。
MVCC 原理
MVCC 是 InnoDB 实现高并发读(快照读 SELECT)的核心机制——读操作不阻塞写操作,写操作也不阻塞读操作。
隐藏列
InnoDB 的每行数据有三个隐藏列:
| 隐藏列 | 含义 |
|---|---|
DB_TRX_ID | 最近修改该行的事务 ID |
DB_ROLL_PTR | 指向 Undo Log 中该行的旧版本 |
DB_ROW_ID | 隐式自增 ID(当表无主键时使用) |
Undo Log 版本链
每次更新操作,InnoDB 不会覆盖旧数据,而是将旧版本写入 Undo Log,形成版本链:
ReadView(读视图)
当事务执行快照读时,生成一个 ReadView,记录当前活跃事务列表。
ReadView 包含:
m_ids:活跃事务 ID 列表(未提交的)min_trx_id:活跃事务中最小 IDmax_trx_id:下一个要分配的事务 IDcreator_trx_id:创建 ReadView 的事务 ID
可见性判断:
-- 对 Undo Log 版本链中某个版本 trx_id:
-- 1. trx_id == creator_trx_id → 自身修改,可见
-- 2. trx_id < min_trx_id → 已提交,可见
-- 3. trx_id >= max_trx_id → 未来事务,不可见
-- 4. min_trx_id <= trx_id < max_trx_id → 查是否在 m_ids 中
-- 在 m_ids 中:未提交,不可见;不在:已提交,可见RR 与 RC 的 ReadView 差异
-- READ COMMITTED:每条 SELECT 都重新生成 ReadView
-- 所以两次 SELECT 可能看到不同的提交结果(不可重复读)
-- REPEATABLE READ:事务内第一次 SELECT 生成 ReadView,之后复用
-- 所以各个 SELECT 看到的版本一致(可重复读)小结
- 隔离级别从低到高:RU → RC → RR → SERIALIZABLE。MySQL 默认 RR,InnoDB 在 RR 下可防幻读。
- MVCC 通过 Undo Log 版本链 + ReadView 实现快照读,无锁并发。
- RR 事务内复用第一次 SELECT 生成的 ReadView;RC 每次 SELECT 重新生成。
- 脏读/不可重复读/幻读分别对应读未提交数据、读到的值变化、读到的行数变化。
上一节:事务基础与 ACID 下一节:锁机制
