Luke a Pro

Luke Sun

Developer & Marketer

🇺🇦
EN||

第五章:并发控制 (MVCC & Locks) — 平行宇宙的魔法

| , 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 解决了读写冲突,但写写冲突依然需要锁。

  1. Auto-inc Lock: 在进行大规模数据插入(INSERT INTO … SELECT)时,MySQL 的自增锁在高并发下成为了瓶颈。
  2. Next-Key Lock: 在 RR(可重复读)隔离级别下,为了防止幻读,插入前的唯一性检查(如 REPLACE INTOINSERT 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;

6. 参考资料