MySQL中快照读读的是基于Undo Log构建的历史版本数据,READ COMMITTED每次SELECT新建read view,REPEATABLE READ仅首次SELECT创建并复用read view。

MySQL 的 READ COMMITTED 和 REPEATABLE READ 隔离级别都依赖“快照读”机制,但它们的快照生成时机和可见性规则完全不同——不是靠锁多锁少决定的,而是靠一致性视图(consistent read view)的构建逻辑。
快照读到底读的是哪个版本?
快照读不等于读内存或读缓存,它读的是基于 Undo Log 构建的历史版本数据。InnoDB 在执行 SELECT(无 FOR UPDATE 或 LOCK IN SHARE MODE)时,会根据当前事务的一致性视图判断某行记录是否可见。
-
READ COMMITTED:每次SELECT都新建一个 read view,因此能看见其他事务已提交的新版本 -
REPEATABLE READ:仅在事务中第一次SELECT时创建 read view,后续查询复用该视图,所以看不到新提交的数据 - 快照读只对普通
SELECT生效;UPDATE/DELETE/SELECT ... FOR UPDATE都是当前读,直接读最新版本并加锁
为什么 REPEATABLE READ 不会出现幻读?
它其实会出现“幻读”,只是 InnoDB 用 Next-Key Lock(间隙锁 + 行锁)在当前读场景下阻止了插入,掩盖了问题;而快照读本身不解决幻读——如果你在事务中两次执行 SELECT COUNT(*),结果可能不同(因为快照里没包含新插入的行),但这不算标准定义的幻读(SQL 标准中幻读指当前读看到新插入行)。
- 真正的幻读风险出现在当前读场景:
SELECT ... FOR UPDATE后别人插入再SELECT ... FOR UPDATE,若没间隙锁就会出现 -
REPEATABLE READ下的快照读“看起来不幻读”,是因为整个事务看到的是一份静态快照,新插入行的DB_TRX_ID大于当前视图的up_limit_id,被过滤掉了 - 别指望快照读防幻读;要真正防止,请用
SELECT ... FOR UPDATE配合唯一条件 or 显式加锁范围
READ COMMITTED 下的非确定性行为容易踩坑
同一事务内多次快照读可能返回不同结果,这在业务逻辑中极易引发状态不一致,比如先查余额再扣款,中间被充值覆盖,就可能导致超扣。
- 典型误用:在
READ COMMITTED下写“先查后更”的逻辑,没加FOR UPDATE,结果查到旧值,更新时覆盖了别人刚提交的修改 - 想安全做“查-改”,必须显式使用当前读:
SELECT ... FOR UPDATE,哪怕隔离级别是REPEATABLE READ -
READ COMMITTED对短事务友好(如 API 单次查询),但对需多步校验的业务操作天然不友好,除非你接受最终一致性
快照读的实现细节藏在 read_view_t 结构里,它保存了 low_limit_id、up_limit_id 和活跃事务 ID 列表;真正决定某行是否可见的,是这行的 DB_TRX_ID 与这些边界值的比较——不是靠 MVCC 版本链长度,也不是靠时间戳。










