跳转到内容

并发控制

并发控制决定并发事务如何不互相破坏。三条路线:悲观锁(先拿锁再干活)、MVCC(多版本,读不阻塞写)、乐观并发(先干活,提交时验证)。本章讲三者的机制与适用负载(→ ACID 与隔离级别)。

2PL 的规则:增长阶段只拿锁不放,收缩阶段只放锁不拿——保证冲突可串行化(数学可证)。

  • 读加共享锁(S),写加排他锁(X),S 与 X 相斥;
  • S2PL(严格 2PL):写锁持有到提交——防级联回滚,是数据库的实际做法;
  • 死锁:2PL 天然产生死锁(→ 并发与同步),数据库用检测(等待图找环,回滚一个受害者)或超时处理——与应用死锁的"预防"不同,数据库选检测,因为锁的粒度细、死锁频率高。

2PL 的代价:读阻塞写、写阻塞读——并发度受限,长事务把锁持有很久。

多版本并发控制(MVCC):每次写产生新版本,读看自己的快照版本——读不阻塞写,写不阻塞读。这是现代数据库(PostgreSQL、MySQL InnoDB、Oracle)的主流:

  • 每行带版本信息(xmin/xmax 或回滚指针);
  • 读事务按快照选择可见版本(→ 快照隔离);
  • 写与写仍冲突(first-updater-wins 或行锁)。

MVCC 是现代数据库的主流,因为它把最常见的冲突(读 vs 写)完全消除了——OLTP 负载大多读多写少,收益巨大。

MVCC 的版本清理(vacuum)问题:旧版本要保留到"所有可能看到它的事务结束"——长事务阻碍版本回收:

  • PostgreSQL:死元组堆积(需要 VACUUM,长事务让表膨胀);
  • MySQL InnoDB:回滚段堆积(长事务让 undo 膨胀);
  • 长事务是 MVCC 系统的第一大敌:监控事务时长是 DBA 的日常。

乐观并发假设冲突少:读阶段无锁干活(记下读集与写集),提交时验证(读集是否被别人改过),冲突则回滚重来。三阶段:读 → 验证 → 写。

  • 冲突少时:无锁开销、无死锁、吞吐最高;
  • 冲突多时:大量回滚重试,吞吐崩溃(活锁)。

乐观并发适合冲突少的负载:低竞争的行(用户自己的数据)、内存数据库(H-Store/VoltDB 的默认)。冲突多的热点(秒杀的同一行)要用悲观锁或队列化。

2PL(悲观)MVCCOCC(乐观)
冲突多的热点好写写仍冲突回滚风暴
读多写少读阻塞写最佳好
长事务锁持有久阻碍回收验证失败率高
实现复杂度中高低

混合是常态:现代数据库 = MVCC(读)+ 行锁(写写冲突)+ 可选的乐观验证(可串行化级别)。

  • 2PL 保证可串行化但读阻塞写;S2PL 持锁到提交;死锁用检测+回滚
  • MVCC 让读不阻塞写,是现代数据库的主流;写写仍冲突
  • 长事务阻碍版本回收:PostgreSQL 表膨胀、InnoDB undo 膨胀
  • 乐观并发适合冲突少的负载;热点行会回滚风暴
  • 混合是常态:MVCC + 行锁 + 可选验证