你在面试里讲事务,不要从"背概念"开始,而要从一个主线开始:
- 事务的目标是:在并发下,既要正确,又要可用。
你必须记住的 3 句话(面试直出):
- 隔离级别解决的是"并发读写会看到什么"的问题,不是"性能开关"。
- InnoDB 的一致性读主要靠 MVCC(版本链 + Read View),而不是每次都加锁。
- 一旦进入"需要锁来保证语义"的场景(如当前读/修改),就要把话题切到:行锁、间隙锁、死锁与锁等待。
1. ACID 里最容易被问透的其实是 I(Isolation)
- A(Atomicity)原子性:要么全成功要么全失败(靠 undo log/回滚机制)
- C(Consistency)一致性:从一个一致状态到另一个一致状态(更偏业务约束 + 事务原子性/隔离性共同保证)
- I(Isolation)隔离性:并发事务之间互不干扰到什么程度
- D(Durability)持久性:提交后不丢(redo log/刷盘策略)
面试常见误区:
- 把"C 一致性"说成数据库自动保证一切业务逻辑正确。
- 忽略"隔离是最核心的并发主题"。
2. 事务并发下的 3 类读现象
统一用这三个现象来串隔离级别:
- 脏读:读到了未提交的数据
- 不可重复读:同一事务内两次读同一行结果不一致(通常是别的事务提交了更新/删除)
- 幻读:同一事务内两次按条件范围查询,结果行数不一致(通常是别的事务插入了满足条件的新行)
你要能一句话区分:
- 不可重复读是"同一行变了"
- 幻读是"符合条件的行集合变了"
3. 隔离级别:别背表格,要讲"代价与实现"
四种隔离级别(从弱到强):
- READ UNCOMMITTED:可能脏读(一般不选)
- READ COMMITTED:不脏读,但可能不可重复读、幻读(Oracle 常见默认)
- REPEATABLE READ:保证一致性读可重复(MySQL InnoDB 常见默认)
- SERIALIZABLE:近似串行(代价最高)
关键点(MySQL/InnoDB 口径最常用):
- 在 RR 下,普通 SELECT 走一致性读(MVCC),不加锁也能"可重复"。
- 幻读这个词要讲清楚:
- 一致性读下,你可能"看不到新插入",因此"表面上不会幻读";
- 但一旦你用了 当前读(for update / update / delete) ,为了避免范围内插入,就会引出 间隙锁/Next-Key Lock。
4. MVCC:你要能讲出"版本链 + Read View"
InnoDB 的 MVCC 不是魔法,抓住两件事:
- 每行有隐藏列 (概念上):
trx_id:最后一次修改该行的事务 idroll_pointer:指向 undo log,形成历史版本链
- Read View:当前事务"允许看到哪些事务的版本"的快照规则
一致性读(普通 SELECT)的直觉:
- 我读的是"在我这个 Read View 下可见的最新版本"。
两句面试加分表达:
- RC 与 RR 的差异,本质是 Read View 生成时机不同(通常 RC 每次读都可能生成新视图,RR 一个事务复用同一视图)。
- MVCC 解决的是"读不阻塞写、写不阻塞读"的大部分场景,但修改类语句仍需要锁。
一个把 RC/RR 讲清楚的最小例子(面试很好用):
- 初始:
account(id=1, balance=100) - T1 开启事务
- T2 开启事务,执行:
UPDATE account SET balance=200 WHERE id=1; COMMIT; - T1 在同一事务里执行两次:
SELECT balance FROM account WHERE id=1;
你要能给出结论:
- 在 RC 下,T1 两次读可能不一样(第二次读到 200)
- 在 RR 下,T1 两次读保持一致(都读到 100),因为复用同一个 Read View
5. 一致性读 vs 当前读:什么时候会加锁
- 一致性读 :普通
SELECT,靠 MVCC 读快照 - 当前读 :读"最新已提交/可见的当前版本",并且通常要加锁
SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODEUPDATE/DELETE/INSERT
你要能讲出这个结论:
- 只要你要"修改/锁定"某些行,就必须进入锁的世界。
再补一个很容易踩坑的对比(线上解释"为什么我 SELECT 看不到,但 UPDATE 又改到了"):
- T1(RR):先普通
SELECT(快照读)看到某行状态=旧值 - T2:更新该行并提交
- T1:再执行
UPDATE ... WHERE ...(当前读)会基于"最新版本"做锁定与修改
结论:
- 同一事务里,快照读看到的是快照;当前读/修改看到的是最新并参与加锁。
- 所以不要用"先 SELECT 校验,再 UPDATE"去想象成一个原子校验;需要的话就用
SELECT ... FOR UPDATE或把校验逻辑写进 UPDATE 条件里。
6. 常见坑:事务讲不清通常是这 4 类问题
-
坑 1:把隔离级别当性能开关
- 隔离越强,冲突越多,锁等待/死锁风险越高。
-
坑 2:以为 MVCC 能解决所有并发问题
- 修改语句必须加锁;范围更新容易引入间隙锁。
-
坑 3:长事务
- 会持有 Read View 太久、undo 变大、锁持有时间长,引发抖动。
-
坑 4:在事务里做慢 IO/远程调用
- 直接放大锁持有时间与冲突。
7. 线上排查:怎么判断是事务隔离/MVCC问题还是锁问题
你可以按这个顺序:
- 先问:是读错了/读不一致 ,还是 卡住了/变慢了?
一个更工程化的分流口径:
- 读错/穿越感:优先怀疑隔离级别、快照读/当前读混用、以及"读后写"未加锁
- 卡住/超时:优先怀疑锁等待、长事务、索引缺失导致锁范围扩大
- 偶发且难复现:优先怀疑并发窗口(例如延迟提交、长事务、跨服务调用在事务里)
7.1 读不一致(数据看起来"穿越")
- 检查是否使用了 RC/RR
- 检查是否混用了"普通 SELECT(快照)"与"for update(当前读)"
7.2 卡住/超时(典型锁等待)
- 现象:接口 RT 拉长、连接数上升
- MySQL 侧:
SHOW PROCESSLIST看Waiting for ... lockinformation_schema.innodb_trx/innodb_locks/innodb_lock_waits(或 performance_schema 相关表)SHOW ENGINE INNODB STATUS看最近死锁
一句话落地:
- 看到"waiting for lock"就不要再谈 MVCC 了,直接进入锁等待/死锁排查链路。
8. 自测清单(你要能顺口讲出来)
-
Q:RR 为什么普通 SELECT 能可重复?
- A:因为 MVCC 的 Read View 让你在同一事务内读到同一快照下可见的版本。
-
Q:幻读和不可重复读怎么区分?
- A:不可重复读是"同一行两次读不一样";幻读是"范围条件两次读,行集合变了"。
-
Q:什么时候一定会加锁?
- A:修改语句或当前读(for update/lock in share mode)需要锁来保证语义。
9. 30 秒背诵稿
事务的核心是并发下的正确性,隔离级别描述的是并发读写会看到什么现象。InnoDB 的普通查询主要用 MVCC 做一致性读,通过版本链和 Read View 在 RC/RR 下提供快照语义;但只要涉及修改或当前读,就必须依赖行锁、间隙锁等机制保证语义。线上读不一致优先检查隔离级别与快照/当前读混用;线上卡顿优先看锁等待与死锁信息。