脏读、不可重复读、幻读是可稳定复现的并发问题:read uncommitted下select直接读未提交数据导致脏读;read committed中同一事务两次select因新readview看到已提交更新造成不可重复读;repeatable read下无索引的当前读(如select ... for update)因间隙锁失效引发幻读。

直接结论:脏读、不可重复读、幻读不是“理论假设”,而是可稳定复现的并发行为,关键在于控制事务隔离级别 + 精确时序 + 使用SELECT或UPDATE触发不同读类型。
用READ UNCOMMITTED复现脏读
脏读只在最低隔离级别下出现,且必须让一个事务读到另一个未提交的修改。
- 会话A执行:
BEGIN; UPDATE accounts SET balance = 900 WHERE id = 1;(不COMMIT) - 会话B执行:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; BEGIN; SELECT balance FROM accounts WHERE id = 1;→ 返回900 - 会话A执行
ROLLBACK;后,会话B再查就是1000(原始值),证明刚才读到的是“脏”数据 - 注意:
READ UNCOMMITTED下SELECT是“当前读”,不走MVCC快照,直接读最新行版本,哪怕未提交
用READ COMMITTED复现不可重复读
不可重复读的核心是:同一事务内两次SELECT,中间被其他事务UPDATE+COMMIT干扰。
- 会话A:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED; BEGIN; SELECT balance FROM accounts WHERE id = 1;(得1000) - 会话B:
BEGIN; UPDATE accounts SET balance = 1200 WHERE id = 1; COMMIT; - 会话A再次执行
SELECT balance FROM accounts WHERE id = 1;→ 返回1200 - 原因:
READ COMMITTED每次SELECT都生成新ReadView,能看到已提交的新版本 - 别用
UPDATE语句复现——它本身会加行锁,阻塞其他写,但不影响读;重点是读操作本身不锁定数据
用REPEATABLE READ复现幻读(需当前读)
InnoDB在REPEATABLE READ下对快照读(普通SELECT)自动防幻读,但当前读(SELECT ... FOR UPDATE、UPDATE、DELETE)仍可能幻读。
- 会话A:
BEGIN; SELECT * FROM orders WHERE status = 'pending';(返回2条) - 会话B:
BEGIN; INSERT INTO orders (status) VALUES ('pending'); COMMIT; - 会话A执行:
SELECT * FROM orders WHERE status = 'pending' FOR UPDATE;→ 返回3条(幻读发生) - 关键点:幻读只在范围查询+当前读+无间隙锁覆盖时触发;
SELECT ... FOR UPDATE在RR下默认加间隙锁,但若查询条件无索引,间隙锁失效,就容易复现 - 验证方式:执行
SELECT * FROM performance_schema.data_locks;看是否真有gap lock生效
真正容易被忽略的点是:幻读复现依赖**查询是否命中索引**。全表扫描的SELECT ... FOR UPDATE几乎无法加有效间隙锁,此时插入新行必然导致幻读——不是InnoDB没做,而是锁粒度失控了。











