第五章:并发控制 (MVCC & Locks) — 平行宇宙的魔法
Published: Thu Feb 05 2026 | Modified: Fri Feb 06 2026 , 2 minutes reading.
1. 定义
MVCC (Multi-Version Concurrency Control) 是一种并发控制方法,常用于数据库中以提供对数据的并发访问。
核心思想是:读写分离,互不阻塞。 当一个事务正在写某行数据时,读事务不会被阻塞,而是会读取该行数据的一个旧版本(快照)。这就像每个人都在看数据的不同“平行宇宙”。
2. 技术深度:Undo Log 与 Read View
以 MySQL InnoDB 为例:
- Undo Log:当事务修改数据时,InnoDB 不会覆盖旧数据,而是将旧数据复制到 Undo Log 中,并通过指针形成版本链。
- Read View:当事务启动时,InnoDB 会生成一个 Read View,记录当前所有“活跃”(未提交)的事务 ID。
- 可见性判断:在读取数据时,事务会拿数据行的版本号与 Read View 对比。如果版本号属于“活跃”事务,说明该版本不可见,于是顺着 Undo Log 找上一个版本。
3. 可视化:快照读 (Snapshot Read)
sequenceDiagram
participant TxA as 事务 A (读取者)
participant TxB as 事务 B (写入者)
participant DB as 数据行 (ID=1)
participant Undo as Undo Log
TxB->>DB: 1. UPDATE ID=1 SET Age=30 (原 Age=20)
Note over DB: 行被锁定 (X-Lock)<br/>Age 变为 30<br/>生成 Undo Log: Age=20
TxA->>DB: 2. SELECT * FROM users WHERE ID=1
Note over DB: 检测到行被 TxB 锁定
DB->>Undo: 3. TxA 读取 Undo Log 中的旧版本
Undo-->>TxA: 4. 返回 Age=20 (快照)
TxB->>DB: 5. COMMIT
Note over DB: 数据 Age=30 正式生效4. 真实案例:GitHub 的主键冲突故障 (2018)
背景:GitHub 在进行 MySQL 数据库迁移时。 现象:网站短暂不可用,写入失败。
原因:Auto-increment 锁竞争与 Next-Key Lock。 虽然 MVCC 解决了读写冲突,但写写冲突依然需要锁。
- Auto-inc Lock: 在进行大规模数据插入(INSERT INTO … SELECT)时,MySQL 的自增锁在高并发下成为了瓶颈。
- Next-Key Lock: 在 RR(可重复读)隔离级别下,为了防止幻读,插入前的唯一性检查(如
REPLACE INTO或INSERT ON DUPLICATE)可能会触发 Next-Key Lock(锁定索引间隙),在极高并发的冲突场景下极易引发严重的锁等待。
教训:MVCC 并不是万能的。在极高并发的写入场景下,必须深入理解锁的粒度。
5. 深度优化与纵深防御
A. 隔离级别的选择
- Read Committed (RC):每次查询都生成新的 Read View。适合对实时性要求高的业务。
- Repeatable Read (RR):事务启动时生成一次 Read View。MySQL 默认级别;通过 Next-Key 锁缓解了“锁定读”场景下的幻读问题,但普通快照读在不同实现下仍可能观察到范围异常。
B. 避免长事务
- 长事务意味着 Read View 需要保留很久。
- 这会导致 Undo Log 无法被清理(Purge),导致系统空间膨胀(History List Length 飙升),进而拖慢所有查询(因为每次都要遍历很长的版本链)。
C. 乐观锁 (Optimistic Locking)
对于非强一致性场景,可以在应用层使用版本号实现乐观锁,从而完全避免数据库层面的行锁。
UPDATE products SET stock = stock - 1
WHERE id = 1 AND version = 5;