目标:你能把隔离级别从"背定义"变成"能推导现象",并解释清楚:
- RR/RC 的一致性读到底读的是什么
- 为什么还会有幻读、什么时候靠锁解决
- MVCC 与当前读的分工
1. 先背现象:四个隔离级别对应什么问题
- RU:可能脏读
- RC:不脏读,但可能不可重复读
- RR:解决不可重复读,但"幻读"需要结合锁语义理解
- Serializable:最强,通常并发能力最差
面试关键不是背表格,而是能举例。
2. 两类读:一致性读(快照) vs 当前读(加锁)
2.1 一致性读(Consistent Read)
- 读的是"某个时间点的快照"
- 不加锁(或加极轻量的元数据/意向锁)
- 依赖 MVCC
2.2 当前读(Current Read)
- 读的是"最新已提交/可见的版本",并且通常会加锁
- 常见语句:
select ... for updateselect ... lock in share modeupdate/delete/insert
直觉:你要修改数据,就必须看"现在的真实值",并锁住它防止并发写。
2.3 对照组:同样一条 select,加不加锁语义完全不同
以 InnoDB 为例:
- 快照读:
select ...(一致性读,走 MVCC) - 当前读:
select ... for update/lock in share mode(加锁读)
面试表达建议:
- "隔离级别 + 读类型"一起决定你看到什么、会不会阻塞别人
3. MVCC 的最小模型:版本链 + Read View
在 InnoDB 的直觉模型里:
- 每行有隐藏字段记录版本信息
- 更新会生成新版本,旧版本通过 undo log 形成版本链
一致性读会生成 Read View(读视图),用它决定:
- 哪些事务的版本对我可见
3.1 RC vs RR 的关键区别(面试常问)
- RC :每次一致性读可能生成新的 Read View
- 所以同一事务内两次查询可能看到不同结果(不可重复读)
- RR :事务第一次一致性读生成 Read View,后续复用
- 所以可重复读
4. 幻读怎么理解:不要只背"RR 解决幻读/没解决幻读"
幻读的经典定义是:
- 同一事务内,两次范围查询返回的"行集合"不同
在 InnoDB 中:
- 单纯一致性读(快照读)在 RR 下通常不会看到新插入行(因为 Read View 复用)
- 但当你做当前读(for update / update)时,需要锁住范围,才防止并发插入导致"范围内出现新行"
因此:
- "幻读"是否出现,取决于你用的是快照读还是当前读
- 要彻底防止并发插入,需要范围锁(next-key)
5. 结合例子推导(建议面试这样讲)
表:t(id primary key, k int, index(k))
最小建表:
sql
create table t (
id int primary key,
k int not null,
v int not null,
key idx_k(k)
);
insert into t values (1, 10, 10);
5.1 RC 的不可重复读
- T1:
select * from t where id=1;读到 v=10 - T2:
update t set v=20 where id=1; commit; - T1:再查 id=1,可能读到 v=20
对照点:RC 允许"同一事务内两次快照读看到不同提交结果",因为每次读可能生成新的 Read View。
5.2 RR 的可重复读(快照读)
- T1:第一次 select 生成 Read View
- T2 更新并提交
- T1 再 select 仍读到旧版本(沿版本链找可见版本)
对照点:RR 的快照读更像"拍了一张照片",后面继续看这张照片,直到事务结束。
5.3 当前读下的"范围问题"
- T1:
select * from t where k between 10 and 20 for update;
这会在 RR 下对范围加 next-key 锁,阻止 T2 插入落在范围内的新行。
一个更具体的并发时序:
- T1:
begin; select * from t where k between 10 and 20 for update; - T2:尝试
insert into t(id,k,v) values(2,15,1);可能会被阻塞
直觉:
- 你既然要对"范围内的数据"做修改或依赖它做业务判断,就必须防止并发插入改变集合
6. 常见坑与排查
-
"我用 RR 还出现数据不一致":
- 你是不是混用了快照读与当前读?
- 是否在同一事务里先 select 后 update,理解版本可见性
-
"范围更新导致锁冲突严重":
- next-key 锁覆盖范围,插入被阻塞
- 用更精确条件或拆批减少锁范围
-
"长事务"导致 undo 膨胀:
- RR 快照长期存在,旧版本无法回收
- 需要控制事务时长
6.1 更流程化的排查 checklist(从现象到机制)
- 先确认"你用的是什么读"
- 普通 select:快照读
for update:当前读 + 加锁
- 先确认隔离级别(RC/RR)
- RC:可能不可重复读(快照随读变化)
- RR:快照固定,但当前读仍会加锁影响并发
- 出现阻塞/死锁时,优先看"范围是否过大"
between/ 非唯一条件 -> next-key 覆盖面大
- 出现历史版本堆积/存储膨胀时,优先怀疑长事务
- 事务时间长会让旧版本无法回收,undo 膨胀
- 需要强一致业务判断时
- 用当前读(for update)并确保 where 足够精确