事务隔离级别与一致性:从现象到实现(MVCC 与当前读)

目标:你能把隔离级别从"背定义"变成"能推导现象",并解释清楚:

  • RR/RC 的一致性读到底读的是什么
  • 为什么还会有幻读、什么时候靠锁解决
  • MVCC 与当前读的分工

1. 先背现象:四个隔离级别对应什么问题

  • RU:可能脏读
  • RC:不脏读,但可能不可重复读
  • RR:解决不可重复读,但"幻读"需要结合锁语义理解
  • Serializable:最强,通常并发能力最差

面试关键不是背表格,而是能举例。

2. 两类读:一致性读(快照) vs 当前读(加锁)

2.1 一致性读(Consistent Read)

  • 读的是"某个时间点的快照"
  • 不加锁(或加极轻量的元数据/意向锁)
  • 依赖 MVCC

2.2 当前读(Current Read)

  • 读的是"最新已提交/可见的版本",并且通常会加锁
  • 常见语句:
    • select ... for update
    • select ... lock in share mode
    • update/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(从现象到机制)

  1. 先确认"你用的是什么读"
    • 普通 select:快照读
    • for update:当前读 + 加锁
  2. 先确认隔离级别(RC/RR)
    • RC:可能不可重复读(快照随读变化)
    • RR:快照固定,但当前读仍会加锁影响并发
  3. 出现阻塞/死锁时,优先看"范围是否过大"
    • between / 非唯一条件 -> next-key 覆盖面大
  4. 出现历史版本堆积/存储膨胀时,优先怀疑长事务
    • 事务时间长会让旧版本无法回收,undo 膨胀
  5. 需要强一致业务判断时
    • 用当前读(for update)并确保 where 足够精确
相关推荐
会飞的大可2 小时前
Elasticsearch:搜索引擎作为 NoSQL 数据库
数据库·elasticsearch·搜索引擎
LaughingZhu2 小时前
Product Hunt 每日热榜 | 2026-04-03
数据库·人工智能·经验分享·神经网络·chatgpt·语音识别
Yushan Bai2 小时前
ORACLE EXADATA的CPU P1 主核心cores 瞬间临时无法被固件注册MCA控制器引起的重启问题分析
数据库·oracle
知识分享小能手2 小时前
MongoDB入门学习教程,从入门到精通,MongoDB从应用程序连接副本集(12)
数据库·学习·mongodb
你才是臭弟弟2 小时前
MongoDB Community Server (社区版)安装流程
数据库·mongodb
X-⃢_⃢-X2 小时前
四、索引的创建与设计原则
数据库·mysql
你才是臭弟弟2 小时前
MongoDB介绍
数据库·mongodb
努力打怪升级3 小时前
使用 pymssql 连接数据库(GBK 编码)乱码问题的完美解决方案
数据库
却话巴山夜雨时i3 小时前
互联网大厂Java面试场景:从Spring到微服务的逐层提问
java·数据库·spring·微服务·日志·性能监控