RR级别下普通SELECT读不到最新数据,因其默认走快照读:事务首次SELECT生成ReadView,后续无锁SELECT均复用该快照,只读取事务启动时已提交的数据版本,不感知其他事务的后续提交。

RR 级别下普通 SELECT 为什么读不到最新数据
因为 RR 默认走「快照读」:事务启动后第一次 SELECT 生成 ReadView,后续所有无锁 SELECT 都复用这个快照,哪怕其他事务已提交新数据,也不会被看到。这不是 bug,是 MVCC 保证可重复读的机制。
常见错误现象:SELECT * FROM users WHERE id = 1 在事务中反复执行,结果始终是旧值,即使另一个事务已 UPDATE users SET name='new' WHERE id=1 并 COMMIT。
关键点:快照读不加锁、不查最新版本、不感知并发写入 —— 它只忠于自己事务开启那一刻的数据视图。
用 SELECT ... LOCK IN SHARE MODE 强制当前读
加上 LOCK IN SHARE MODE 后,语句就从快照读升级为「当前读」:InnoDB 会读取记录最新已提交版本,并加一个共享锁(S 锁),同时触发 Next-Key Lock(记录锁 + 间隙锁)防止幻行插入。
实操建议:
- 语法必须完整:
SELECT * FROM t WHERE id = 1 LOCK IN SHARE MODE;漏掉空格或大小写无关,但LOCK IN SHARE MODE是固定短语,不能拆成LOCK IN SHARE或省略MODE - 它和
SELECT ... FOR UPDATE的区别在于锁类型:前者允许其他事务加 S 锁(可并发读),后者加 X 锁(阻塞所有并发读写) - 如果 WHERE 条件没走索引,会升级为表级锁(或全索引扫描+大量间隙锁),性能风险极大
- 执行后若遇到锁等待,会阻塞直到超时(默认
innodb_lock_wait_timeout=50秒),不是立即报错
当前读能读到哪些“最新”数据
「最新」仅指「已提交」的最新,不包括未提交或已回滚的数据。但要注意边界情况:
容易踩的坑:
- 事务 A 执行
SELECT ... LOCK IN SHARE MODE读到某行,事务 B 同时DELETE该行并COMMIT,事务 A 再次执行相同当前读会查不到该行 —— 这不是幻读,是正常行为 - 事务 A 当前读命中了间隙(比如
WHERE age > 30),而事务 B 插入age = 35的新行,事务 A 会被阻塞(Next-Key Lock 生效),除非 B 先 rollback 或 A 等待超时 - 如果事务 B 插入的是
age = 25(不在 A 的扫描范围内),A 的当前读完全不受影响 —— 间隙锁只锁住 WHERE 谓词覆盖的索引范围
性能影响:当前读要加锁、查聚簇索引最新页、可能触发磁盘 IO,比快照读开销明显更高,不要在高并发只读场景滥用。
替代方案与适用场景
并非所有需要“最新数据”的场景都适合 LOCK IN SHARE MODE。根据目的选择:
- 只是为了避免脏读、又不需要锁 —— 改用 RC 隔离级别,每次
SELECT都是当前读且无锁(但会失去可重复读语义) - 要更新某行,且必须基于最新值做判断 —— 直接用
UPDATE ... WHERE,它本身就是当前读,无需先SELECT再UPDATE(避免 AB-BA 死锁) - 要校验某条记录是否存在并防止并发插入 ——
SELECT ... FOR UPDATE更合适,因为后续大概率要INSERT或UPDATE - 只是想跳过快照、看一眼最新状态,不打算做任何修改 ——
LOCK IN SHARE MODE是最轻量的当前读方式,比FOR UPDATE对并发更友好
真正容易被忽略的是:LOCK IN SHARE MODE 的锁持续到事务结束(COMMIT 或 ROLLBACK),不是语句执行完就释放。长事务中频繁使用,会显著拉长锁持有时间,增加死锁概率。


















