同一个事务里按 status = 1 查了两次,第二次多出 3 条 id 更大的记录,两次查询之间这个事务什么都没写。隔离级别是 REPEATABLE READ。乍看像缓存或者权限的锅,追下去往往不是。
SELECT @@transaction_isolation; START TRANSACTION; SELECT COUNT(*) FROM orders WHERE status = 1 FOR UPDATE; COMMIT;
追下去是这么回事:快照读看到的是事务开始时的版本,当前读拿的却是最新数据,同一条 SQL 在不同读法下结果就不一样。范围条件没有唯一索引时,间隙锁只锁已有区间,新插入的行仍可能落进来。
「幻读」相关的日志我一般连着看前后五分钟,单看一条报错经常判断不出是哪一步失败。接口问题先分清是没发出去、发出去了没收到,还是收到了处理失败。
具体做法是,确认业务里有没有混用快照读和当前读 比如普通 select 后面跟了 for update;范围条件所在的列建索引 让间隙锁的范围可控;需要强一致的统计放进同一个当前读里完成;把隔离级别和读法写进代码注释。
验收就看一点,同一事务两次统计结果一致 并发插入的新行要么被锁住要么等事务结束后才可见。
