事务与锁:综览
事务的隔离性不是靠单一机制实现的,而是「MVCC 管读、锁管写」两套机制分工配合。理解事务与锁,先建立这条主线,再看读和写各自走哪条路径。
1. 一条主线:隔离性 = MVCC + 锁
ACID 四项承诺里,隔离性(Isolation)要回答:两个事务并发读写同一份数据时,彼此能看到对方的哪些中间状态。InnoDB 用两套机制共同回答:
| 机制 | 管什么 | 怎么管 | 载体 |
|---|---|---|---|
| MVCC | 读(快照读) | 每行保留多个版本,读时按 Read View 挑一个可见版本,不加锁 | Undo Log 版本链 |
| 锁 | 写,以及「要读最新值」的读 | 在索引记录上加行锁,阻塞冲突操作 | Record / Gap / Next-Key Lock |
为什么必须两套配合,单靠锁不够?锁只能挡住「正在改」,挡不住「改完提交了」。要挡住后者(可重复读),必须让读操作读到「事务开始那一刻的快照」而非「数据库最新状态」——这是 MVCC 的职责。反过来,写操作必须基于最新状态:两个事务若都基于同一份旧快照做 balance = balance - 100,会互相覆盖——这是锁的职责。
2. 一条 SQL 的路径分叉
每一句 SQL 进来,先分「读」还是「写」,读再分两条:
- 快照读:普通
SELECT,走 MVCC,读历史可见版本,不加锁、不被写阻塞。 - 当前读:
SELECT ... FOR UPDATE/SELECT ... LOCK IN SHARE MODE,绕过 MVCC,读最新版本并加锁。 - 写:
INSERT/UPDATE/DELETE,本质先做一次当前读,再加锁。
3. 一笔并发读写里,三件事如何配合
用 事务与 MVCC 开头的场景串一遍:
- 事务 B
UPDATE(写):先做当前读拿到最新行、加行锁;同时把旧值写进 Undo Log,版本链多一节。 - 事务 A 普通
SELECT(快照读):走 MVCC,用 Read View 沿版本链判定可见版本,读到旧值 500,不加锁、不被 B 阻塞。 - 事务 B
COMMIT:释放行锁;它留下的 Undo 旧版本,等所有 Read View 都不再需要后由 Purge 清理。
这三步把三件事的关系讲清:Undo Log 提供历史版本(MVCC 的数据基础),Read View 判定该读哪个版本,锁保护写与写不冲突。