READ COMMITTED 下每次 SELECT 重建 Read View 的根本原因是其采用语句级快照读,每次执行生成新 Read View,导致两次查询可能看到不同已提交数据,从而产生设计允许的不可重复读。

READ COMMITTED 下每次 SELECT 都重建 Read View
根本原因在于:READ COMMITTED 隔离级别下,SELECT 是语句级快照读,每次执行都会生成一个新的 Read View,而不是事务级复用。这意味着两次 SELECT 看到的“已提交数据集合”可能不同——只要中间有其他事务提交了修改,第二次查询就可能看到新值。
不可重复读在 READ COMMITTED 中是设计行为,不是 Bug
这不是实现缺陷,而是 SQL 标准和 MySQL 的明确约定。以下情况会直接触发不可重复读:
- 事务 A 执行
SELECT * FROM account WHERE id = 1,读到balance = 100 - 事务 B 执行
UPDATE account SET balance = 200 WHERE id = 1并COMMIT - 事务 A 再次执行相同
SELECT→ 返回balance = 200
因为第二次 SELECT 构建的新 Read View 包含了事务 B 的 trx_id(它已提交),所以该行最新版本对 A 可见。
与 REPEATABLE READ 的关键区别:Read View 生命周期
对比之下,REPEATABLE READ 在事务中**第一次快照读时生成 Read View,并全程复用**,因此后续所有普通 SELECT 都基于同一快照,自然屏蔽了中间提交。
而 READ COMMITTED 没有这种“事务锚点”,它的隔离粒度更细、更激进——宁可多读一点新数据,也不愿为一致性锁住视图。这也带来实际影响:
-
UPDATE ... WHERE语句在 READ COMMITTED 下也按语句执行前的最新状态匹配行(即当前读),但条件判断依据仍是刚生成的 Read View,所以不会出现“查得到、更新不了”的错位 - 高并发计数类场景(如库存扣减)若依赖两次
SELECT差值做判断,在 READ COMMITTED 下极易因中间变更导致逻辑错误 - 无法通过简单加
SELECT ... FOR UPDATE来“修复”不可重复读问题——它解决的是写冲突,不改变普通读的语义
确认当前会话隔离级别是否真为 READ COMMITTED
常见误判来源是连接复用或隐式 autocommit:
- 执行
SELECT @@transaction_isolation,结果应为'READ-COMMITTED';若返回'REPEATABLE-READ',说明设置未生效 - 使用 ORM(如 Django、SQLAlchemy)时,
autocommit=True会让每个语句独占事务,此时“不可重复读”现象会更明显,且不属于事务内问题 - Navicat / DBeaver 等客户端开启“自动提交”后,
BEGIN后未显式COMMIT就执行新查询,可能意外退出事务上下文,退化为单语句执行
真正难处理的,不是“为什么读到了新值”,而是业务代码把两次 SELECT 当成原子前提来用——这种假设在 READ COMMITTED 下天然不成立。


















