第 7 章 · 事务与并发控制

事务凭什么「要么全成、要么全不」,多事务并发为什么不出乱子。

7.1 ACID:事务的四个承诺

性质含义破坏者
A 原子性事务内操作要么全做、要么全不做中途崩溃(第 8 章恢复解决)
C 一致性把库从一个一致状态带到另一个一致状态约束被违反
I 隔离性并发事务互不干扰,像串行执行并发异常(本章主题)
D 持久性一旦提交,结果永久保存掉电、介质损坏(第 8 章)

原子性靠 undo,持久性靠 redo——详见第 8 章。本章聚焦 I(隔离性) 与并发下的 A/C 维系。

7.2 并发异常:不隔离会怎样

异常描述示例
脏读读到别的事务未提交的修改T2 读到 T1 改了但还没 commit 的余额
不可重复读同一事务两次读,值被别的事务两次读余额不一样
幻读同一查询两次,行集合变了(多/少了行)第二次多出别的事务插入的行
丢失更新两个事务都改同一值,一个覆盖另一个两人同时扣款,一笔被吞

根因:事务交叉执行破坏「串行等价」。解法:封锁或 MVCC。

7.3 封锁:共享锁与排他锁

  • 共享锁(S 锁,读锁):允许多事务同时读,不能写。
  • 排他锁(X 锁,写锁):独享,读写都禁止他人。
        S 锁      X 锁
S 锁    兼容    冲突
X 锁    冲突    冲突

两段锁协议(2PL):事务分「增长段」(只加锁)和「缩减段」(只放锁),先统一加锁、后统一释放。遵守 2PL 保证可串行化,但不解决死锁(靠死锁检测/超时/等待图)。

7.4 隔离级别:在正确性与性能间取值

级别脏读不可重复读幻读实现
READ UNCOMMITTED可能可能可能几乎不锁
READ COMMITTED杜绝可能可能读快照/行锁提交即放
REPEATABLE READ杜绝杜绝可能*快照读 + 间隙锁
SERIALIZABLE杜绝杜绝杜绝全串行/严格 2PL

*MySQL InnoDB 在 REPEATABLE READ 下靠间隙锁(gap lock)基本杜绝幻读。隔离级别越高,一致性越强,并发吞吐越低。

7.5 MVCC:多版本并发控制

MySQL InnoDB 与 PostgreSQL 靠 MVCC 而非全程加锁:

undo 日志里保留每一行的历史版本,链成一个版本链
读事务看到的是「事务开始时刻的快照」——
  版本 trx_id ≤ 当前活跃最小事务号,且未被活跃事务修改
  • 读不阻塞写、写不阻塞读:读直接读旧版本,不等写锁。
  • 每行带隐藏列 trx_id(最后修改者)和回滚指针(指向 undo 旧版本)。
  • 代价:旧版本要留着(undo 膨胀);MVCC 只解决读,写-写冲突仍靠锁。

对比:四个隔离级别 + MVCC——READ UNCOMMITTED 几乎无保护、吞吐最高;READ COMMITTED 是 Oracle/PG 默认,避免脏读但允许不可重复读;REPEATABLE READ 是 InnoDB 默认,快照读 + 间隙锁兼顾一致与吞吐,OLTP 甜点;SERIALIZABLE 最安全最慢。MVCC 让读走快照免锁——用「多版本 + undo 空间」换「少加锁 + 高并发」。