mysql的repeatable read下select与delete快照不一致是设计使然:select读事务启动时的快照,而delete等dml直接读最新已提交数据,导致“查不到却能删掉”;验证需查@@transaction_isolation和innodb_trx;统一行为应显式加锁或改用read-committed。

因为 MySQL 的 REPEATABLE READ 隔离级别下,普通 SELECT 不加锁,只读快照;但 DELETE、UPDATE 等 DML 操作会看到并作用于最新已提交的数据——这打破了“查询结果完全隔离”的直觉。
REPEATABLE READ 下 SELECT 和 DELETE 的快照不一致
在 InnoDB 默认的 REPEATABLE READ 隔离级别中,事务启动时会建立一个一致性读视图(consistent read view),后续所有普通 SELECT 都基于这个快照。但 DML 语句(如 DELETE、UPDATE)不是这样:
-
SELECT * FROM t WHERE c1 = 'xyz';返回 0 行 → 因为快照里没有这些行 -
DELETE FROM t WHERE c1 = 'xyz';却可能删掉几行 → 它实际扫描的是最新已提交数据,而非当前事务快照 - 这种“查不到却能删掉”的现象,是设计使然,不是 bug
为什么 DELETE 能绕过快照?
MySQL 对 DML 的处理逻辑和纯查询不同:它必须定位到物理行才能加锁或修改,所以会跳过 MVCC 快照,直接访问聚簇索引中最新版本的行(只要该行已提交)。这也意味着:
- 同一个事务里,
SELECT和DELETE看到的数据范围可能不一致 - 如果其他会话刚
COMMIT了一批c1 = 'xyz'的行,你的DELETE就能命中它们,哪怕你自己的SELECT还看不到 - 这种行为在
READ COMMITTED下更明显(每次SELECT都新建快照),但在REPEATABLE READ下容易被误认为“异常”
如何验证当前会话的隔离级别和快照状态?
别猜,直接查:
- 看当前隔离级别:
SELECT @@transaction_isolation;(MySQL 8.0+ 返回类似REPEATABLE-READ) - 确认是否有长事务拖着旧快照:
SELECT trx_id, trx_state, trx_started FROM information_schema.INNODB_TRX WHERE trx_state = 'RUNNING'; - 检查是否被其他事务阻塞:
SELECT * FROM performance_schema.data_locks;或SHOW ENGINE INNODB STATUS\G
想让 SELECT 和 DELETE 行为一致,怎么办?
唯一可靠的方式是显式加锁,强制 DML 和查询走同一套可见性逻辑:
- 用
SELECT ... FOR UPDATE替代普通SELECT,这样后续DELETE就不会“意外命中”新提交的行 - 或者把隔离级别临时设为
READ COMMITTED:SET SESSION transaction_isolation = 'READ-COMMITTED';(注意:连接池可能复用旧连接,设置未必生效) - 避免在同一个事务里混用“无锁查”和“有副作用删”,尤其是涉及范围条件(如
WHERE c1 = 'xyz')时
最常被忽略的一点是:事务中执行 DELETE 前,其实已经放弃了 MVCC 快照的保护——它主动去碰最新数据了。这不是配置问题,而是 InnoDB 对 DML 的底层语义决定的。











