Transactions, Locks and MVCC Explained

ACID implementation, isolation levels and phenomena, MVCC snapshots, row locks, gap locks and deadlock handling in InnoDB.

AdvancedUpdated 2026-10-2857K views

How ACID is Implemented

ACID is not magic: Atomicity comes from the undo log (rollback on failure or abort), Durability from the redo log with write-ahead logging (a page is durable once its redo is flushed), Isolation from locks plus MVCC, and Consistency is the outcome of the other three plus application constraints.

修改一行数据的内部流程:
1. 在缓冲池找到页(未命中则从磁盘读)
2. 生成 undo log(记录旧值)→ 可回滚
3. 更新页数据(脏页)
4. 生成 redo log 并顺序写入 log buffer
5. redo log 刷盘(innodb_flush_log_at_trx_commit=1)
6. 提交成功;脏页由后台线程择机写回磁盘

→ 日志先行(WAL):保证崩溃后 redo 能重放出已提交数据

Isolation Levels and the Phenomena

级别脏读不可重复读幻读
READ UNCOMMITTED可能可能可能
READ COMMITTED (RC)不可能可能可能
REPEATABLE READ (RR,InnoDB 默认)不可能不可能InnoDB 基本不可能
SERIALIZABLE不可能不可能不可能
  • Dirty read: reading another transaction's uncommitted modification, which can roll back.
  • Non-repeatable read: the same query twice in one transaction sees different committed values.
  • Phantom read: the same range query returns extra rows inserted by others.
  • Rc reads the latest committed version each time — for gap prevention and statement-consistent reports pick RR.

MVCC: Read Without Locks

每行额外字段:trx_id(最后修改它的事务 id)、roll_pointer(指向 undo 链)

一致性视图(Read View):事务开启时记录「活跃事务列表」
判断版本可见性:
  trx_id < min(活跃)        该版本在快照前已提交 → 可见
  trx_id > max(已分配 id)   快照后才改的       → 不可见
  在活跃列表里               未提交             → 不可见
  不在列表且 <= max         已提交             → 可见

RC:每条 SELECT 都建新快照 → 总是读到最新已提交
RR:复用第一条 SELECT 的快照 → 整事务读结果一致

不可见时沿 roll_pointer 走 undo 链,找最近可见版本

Row Locks, Gap Locks and Next-Key

-- RR 下等值命中唯一索引:退化为行锁
SELECT * FROM users WHERE id = 10 FOR UPDATE;   -- 锁 id=10 这一行

-- 等值未命中(如 id=11 不存在):间隙锁 (10, 20)
-- 防止别人在间隙里插入 → 防幻读

-- 范围查询:Next-Key Lock(记录锁 + 前面间隙)
SELECT * FROM orders WHERE id BETWEEN 10 AND 20 FOR UPDATE;
-- 锁 (5,10] 与 (10,20] 及其间的插入

-- 都在「主键等值唯一」场景尽量用:锁最少
-- 非唯一索引等值:锁多条记录 + 间隙

-- 显式加锁
SELECT ... FOR UPDATE;        -- 排他锁(写前)
SELECT ... LOCK IN SHARE MODE;-- 共享锁(少用)

-- 查看锁
SELECT * FROM performance_schema.data_locks;
-- 乐观锁:版本号 / 时间戳,适合低竞争
UPDATE goods SET stock = stock - 1, version = version + 1
WHERE id = 7 AND version = 5;     -- 受影响行数 = 0 则重试

-- 悲观锁:竞争激烈、重试成本高时
BEGIN;
SELECT stock FROM goods WHERE id = 7 FOR UPDATE;
-- 判断库存足够
UPDATE goods SET stock = stock - 1 WHERE id = 7;
COMMIT;

Deadlocks: Detect and Cure

死锁本质:两个事务互相等待对方释放锁,形成环
  事务 A:锁行 1 → 想锁行 2
  事务 B:锁行 2 → 想锁行 1

InnoDB 主动检测:等待图出现环即回滚「代价小」的事务
(innodb_deadlock_detect 默认开启,热点行上自查成本高)
  • Fix the classic cause: tables locked in different order — enforce a global update order (sort by primary key).
  • Shrink transactions: fewer statements, no RPC inside, shorter locks.
  • Lower isolation (RR→RC) reduces gap locks; use unique indexes so equal-value locks degrade to row locks.
  • High-concurrency hot rows: optimistic retry batching or queue serialization beats row locking.
  • Watch deadlocks: SHOW ENGINE INNODB STATUS, or log ERROR 1213 and retry programmatically.

Friends & Partners