幻读快照读与当前读

实验二:不可重复读复现 → RR 解决

实验目的:亲眼看到同一事务内两次读同一条数据——值不同。

2.1 不可重复读复现(READ COMMITTED)

 
-- ========== 两个 Session 都执行 ==========
 
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
 
 
 
-- ========== Session B ==========
 
BEGIN;
 
SELECT id, name, age FROM users WHERE id = 1;
 
-- +----+------+-----+
 
-- | id | name | age |
 
-- +----+------+-----+
 
-- |  1 | 张三 |  25 |   ← 第一次读:25
 
-- +----+------+-----+
 
 
 
-- ========== Session A ==========
 
BEGIN;
 
UPDATE users SET age = 30 WHERE id = 1;  -- 把张三年龄改成 30
 
COMMIT;  -- 提交!
 
-- 切回 B
 
 
 
-- ========== Session B ==========
 
SELECT id, name, age FROM users WHERE id = 1;
 
-- +----+------+-----+
 
-- | id | name | age |
 
-- +----+------+-----+
 
-- |  1 | 张三 |  30 |   ← 第二次读:30!
 
-- +----+------+-----+
 
-- 同一个事务内,两次读同一条数据——值不一样!
 
-- 这就是不可重复读
 
 
 
ROLLBACK;
 
 
 
-- 恢复数据
 
-- Session A:
 
UPDATE users SET age = 25 WHERE id = 1;
 
COMMIT;
 

graph TD

    Result["📋 实验结果:READ COMMITTED 下<br/><br/>第一次 SELECT → age = 25<br/>中间 A 修改并提交<br/>第二次 SELECT → age = 30<br/><br/>⚠️ 不可重复读的本质:<br/>别人 UPDATE 并提交了 → RC 承认已提交的数据 → 读到了新值<br/><br/>🌰 现实影响:<br/>你的事务里先查余额 100,准备扣款<br/>中间另一事务扣了 50 并提交<br/>你再查余额 50——你基于 100 做决策,但实际只剩 50"]

    style Result fill:#fff3cd,stroke:#ffc107,stroke-width:2px

2.2 RR 解决不可重复读

 
-- ========== 两个 Session 都执行 ==========
 
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
 
 
 
-- ========== Session B ==========
 
BEGIN;
 
SELECT id, name, age FROM users WHERE id = 1;
 
-- +----+------+-----+
 
-- | id | name | age |
 
-- +----+------+-----+
 
-- |  1 | 张三 |  25 |   ← 第一次读:25。RR 在这时建立了 Read View
 
-- +----+------+-----+
 
 
 
-- ========== Session A ==========
 
BEGIN;
 
UPDATE users SET age = 40 WHERE id = 1;
 
COMMIT;  -- 提交!换成 RC 的话 B 马上就能看到变化
 
 
 
-- ========== Session B ==========
 
SELECT id, name, age FROM users WHERE id = 1;
 
-- +----+------+-----+
 
-- | id | name | age |
 
-- +----+------+-----+
 
-- |  1 | 张三 |  25 |   ← 还是 25!Read View 锁定了事务开始时的数据
 
-- +----+------+-----+
 
 
 
-- A 把 age 改成 40 并提交了——但 B 就是看不到
 
-- 因为 RR 在整个事务期间使用同一个 Read View
 
 
 
ROLLBACK;
 
 
 
-- Session A: 恢复数据
 
UPDATE users SET age = 25 WHERE id = 1;
 
COMMIT;
 

graph TD

    Result["📋 实验结果:REPEATABLE READ 下<br/><br/>第一次 SELECT → age = 25<br/>中间 A 修改并提交<br/>第二次 SELECT → age = 25(还是 25!)<br/><br/>✅ RR 防住了不可重复读——同一事务内每次读到的一样<br/><br/>原理:RR 在事务开始时建一个 Read View,<br/>整个事务期间都用这一个快照——别人提交了我也当没看见"]

    style Result fill:#d4edda,stroke:#28a745,stroke-width:2px


速记卡(面试闪卡)

Q1:一句话讲清「幻读快照读与当前读」到底是什么?

A:不可重复读指同一事务内两次读同一行结果不同,MySQL 的 RR 隔离级别靠 Read View 快照解决。

Q2:2.1 不可重复读复现(READ COMMITTED) —— 怎么理解?

A:READ COMMITTED 下复现:事务 B 先查 age=25,事务 A 把 age 改成 30 并提交,B 再查变成 30——同一事务内两次读同一条不一样,这就是不可重复读。本质是别人 UPDATE 并提交后,RC 承认已提交数据,读到了新值(你余额 100 准备扣款,中间被改成 50,决策基于错的旧值)。

Q3:2.2 RR 解决不可重复读 —— 怎么理解?

A:REPEATABLE READ 在事务开始时建一个 Read View(一致性快照,英文 Consistent Snapshot),整个事务期间用同一个。A 把 age 改成 40 并提交,B 再查还是 25——Read View 锁定了事务开始时的数据,别人提交了也当没看见。所以 RR 防住了不可重复读。

Q4:快照读与当前读的区别 —— 怎么理解?

A:快照读(普通 SELECT)读 Read View 里的旧值,不加锁;当前读(SELECT … FOR UPDATE、UPDATE、DELETE)读最新已提交并加锁,防幻读。RR 靠 MVCC(多版本并发控制,英文 Multi-Version Concurrency Control)快照实现可重复读,靠当前读加间隙锁防幻读。

Q5:RR 与 RC 的实战取舍 —— 怎么理解?

A:RC 并发高但会出现不可重复读/幻读;RR 是 MySQL 默认、靠 Read View 保一致,适合读多写少或要求强一致。注意 RR 下普通 SELECT 是快照读,要拿实时数据得用当前读(FOR UPDATE)。隔离级别越高一致性越强、并发开销越大,按业务容忍度选择。

Q6:核心速记主线有哪些?

  • 不可重复读:同事务两次读同一行值不同(RC 下复现)

  • RR 靠事务开始时的 Read View 快照,全程同一视图

  • 快照读(普通 SELECT)读旧值不加锁;当前读读最新并加锁

  • RR 是 MySQL 默认,MVCC 保一致、间隙锁防幻读

口诀

A:不可重复读,RC 下现原形;

RR 建快照,Read View 锁全程;

快照读旧值不锁,当前读取新并加锁;

MySQL 默认 RR,MVCC 保一致。

相关链接