MYSQL RR 解决“脏读+不可重复读“和“幻读“的本质区别

RR 解决脏读 + 不可重复读 和 RR 解决幻读:MVCC 解决快照读。这两个是否矛盾

一、先说结论:完全不矛盾

问题 解决机制 解决哪类读
脏读 + 不可重复读 MVCC(ReadView 复用) 单行读
幻读 MVCC(快照读) 范围读
幻读(当前读) Next-Key Lock 范围读 + 锁

关键洞察:

  • MVCC 一个机制,既解决了不可重复读,又解决了快照读的幻读
  • 两种场景都是"读快照" ,RR 下 ReadView 复用 → 看不到其他事务的新数据
  • 不可重复读和幻读本质相同 :都是"读不到其他事务新数据 "------单行级别 就是不可重复读,范围级别就是幻读

二、3 大问题本质

不可重复读:单行被修改
复制代码
-- 老哥的报表例子
BEGIN;
SELECT balance FROM account WHERE id = 1;  -- 第 1 次读,balance=1000
-- 其他事务:UPDATE account SET balance = 900 WHERE id = 1; COMMIT;
SELECT balance FROM account WHERE id = 1;  -- 第 2 次读,balance=900
-- ⚠️ 同一事务,同一行,结果不同 → 不可重复读
幻读:范围被插入
复制代码
-- 老哥的批量报表例子
BEGIN;
SELECT * FROM account WHERE balance > 1000;  -- 第 1 次查,5 条
-- 其他事务:INSERT INTO account (balance) VALUES (2000); COMMIT;
SELECT * FROM account WHERE balance > 1000;  -- 第 2 次查,6 条
-- ⚠️ 同一事务,同一范围,行数不同 → 幻读

本质对比:

维度 不可重复读 幻读
关注点 单行被修改 范围被插入/删除
结果变化 值变了 行数变了
底层机制 都是 MVCC ReadView 复用 都是 MVCC ReadView 复用
区别 同一行的不同版本 范围中多了/少了行

**所以------MVCC 解决不可重复读,自然就解决了快照读的幻读。因为它们都是"看不到新数据"。

三、MVCC 一个机制,两个效果

复制代码
RR 隔离级别下:
- 事务开始第一次 SELECT 时创建 ReadView
- 整个事务期间复用这个 ReadView
- 看不到 ReadView 之后才提交的数据
         ↓
  看不到"被修改的数据"(不可重复读解决)
         ↓
  看不到"被插入的数据"(幻读解决)

一句话总结:

"MVCC 看不到 ReadView 之后的新数据 ,自然就既解决不可重复读(修改)又解决幻读(插入)。一个机制,两个效果。"

四、为什么会有"矛盾"的感觉?

感觉矛盾,可能是因为4 大隔离级别的标准定义:

隔离级别 解决 没解决
RU 无 脏读 / 不可重复读 / 幻读
RC 脏读 不可重复读 / 幻读
RR(标准) 脏读 / 不可重复读 幻读
SE 全部 无

标准 SQL 定义里 RR 是不解决幻读的 !但MySQL InnoDB 通过 MVCC + Next-Key Lock 突破了标准定义,几乎解决了幻读。

所以老哥看到的两个说法:

1."RR 解决脏读 + 不可重复读"(标准 SQL 定义)

2."RR 解决幻读"(MySQL InnoDB 实际实现)

它们都对!只是描述的角度不同:

  • 角度 1:按标准 SQL 定义,RR 不解决幻读
  • 角度 2 :按 MySQL InnoDB 实现,RR 通过 MVCC + Next-Key Lock 几乎解决幻读

五、RR 解决幻读的 2 大机制 (MVCC 解决快照读 + Next-Key Lock 解决当前读)

机制 1:MVCC 解决快照读的幻读 (普通 SELECT)
复制代码
-- RR 隔离级别 + 普通 SELECT
BEGIN;
-- 创建 ReadView,假设 m_ids=[2,3,4,5], min=2, max=6
SELECT * FROM account WHERE balance > 1000;  -- 看到 5 条

-- 期间事务 6 INSERT 并提交一条
-- 事务 6 的 trx_id=6 > max=6,不在 ReadView 范围内
SELECT * FROM account WHERE balance > 1000;  -- 仍看到 5 条(ReadView 复用)
COMMIT;

关键 :ReadView 看不到 trx_id > max_trx_id 的事务提交的数据 ,所以新插入的行看不到 ,幻读解决。

机制 2:Next-Key Lock 解决当前读的幻读 (SELECT FOR UPDATE)
复制代码
-- RR 隔离级别 + 当前读
BEGIN;
SELECT * FROM account WHERE balance > 1000 FOR UPDATE;  -- 加 Next-Key Lock
-- 锁定范围:balance > 1000 涉及的索引区间

-- 期间其他事务尝试 INSERT balance > 1000
INSERT INTO account (balance) VALUES (2000);  -- ⚠️ 阻塞!

SELECT * FROM account WHERE balance > 1000;  -- 仍看到 5 条
COMMIT;

关键 :Next-Key Lock = 记录锁 + 间隙锁 ,锁定了"可能插入的位置" ,防止新数据插入。

六、面试话术

"不矛盾。

'RR 解决脏读 + 不可重复读' 是标准 SQL 定义------按 SQL 标准 RR 不解决幻读。

'RR 解决幻读' 是MySQL InnoDB 实际实现 ------InnoDB 用 MVCC 解决快照读幻读 (ReadView 复用),用 Next-Key Lock 解决当前读幻读(记录锁+间隙锁)。

核心洞察 :不可重复读和幻读本质相同 ------都是看不到其他事务的新数据 。单行级别 叫不可重复读,范围级别 叫幻读。MVCC 一个机制同时解决。"

七、项目实战对照

RR 默认
复制代码
@Transactional  // RR
public void generateReport(Report report) {
    // 1. 单行查(不可重复读解决)
    Report existing = reportMapper.selectById(report.getId());
    // 整个事务期间,existing 不会被其他事务的修改影响
    
    // 2. 范围查(幻读解决)
    List<Report> pending = reportMapper.selectByStatus("pending");
    // 整个事务期间,pending 不会被其他事务的插入影响
    
    // 3. 当前读(Next-Key Lock 解决幻读)
    List<Report> all = reportMapper.selectByStatusForUpdate("pending");
    // 加锁,其他事务不能 INSERT status='pending' 的报表
}
RC 查询
复制代码
@Transactional(isolation = Isolation.READ_COMMITTED)  // RC
public List<MaskedData> queryLatestMasked() {
    // 1. 单行查(能重复读 → 老数据)--- mpvs 不在意
    // 2. 范围查(能幻读 → 新数据)--- mpvs 在意,要看最新
    
    return dataMaskMapper.selectAll();  // 看到最新
}

用 RR :不可重复读 + 幻读都不能有(同一报表要一致)。

用 RC :能看到最新(有些任务要看最新数据)。"

八、记忆口诀

"不可重复读和幻读,本质都是看不到新数据"

"单行级别 = 不可重复读,范围级别 = 幻读"

"MVCC 一个机制,同时解决两个"

"标准 SQL RR 不解决幻读,MySQL InnoDB 解决了"

"快照读靠 MVCC,当前读靠 Next-Key Lock"

相关推荐
小白男神4 天前
MySQL进阶学习四(存储过程、游标、触发器)
mysql
这个DBA有点耶4 天前
MVCC深入:Read View、版本链与快照读——InnoDB并发控制的内核
数据库·mysql·架构
DBA_G4 天前
从地面到云霄:GBase数据库在民航三大场景的落地实践
数据库
自由能燃气设备4 天前
商用全预混低氮冷凝锅炉免费方案vs付费方案对比+选型避坑指南
大数据·数据库·人工智能
科创致远4 天前
科创致远 ESOP 系统核心效能与实战价值展示
大数据·数据库·人工智能·精益工程
kybs19914 天前
全球灾害数据分析可视化 毕业设计-附源码66794
vue.js·spring boot·mysql·安全·django·c#·asp.net
2601_962218614 天前
万象生鲜系统称重自动多退少补算法解决生鲜非标品痛点
大数据·数据库·人工智能·python·算法
程序猿_极客4 天前
【免费】分享一套优质的基于SSM的校园失物招领系统的设计与实现,源码+文档+视频详解(讲解)
java·mysql·ssm·课程设计·失物招领系统
张洛闻Eren4 天前
k8s云原生【第十课】:水平 Pod 自动扩缩容
运维·数据库·云原生·kubernetes·github
于平安4 天前
MySQL-触发器
数据库·mysql