幻读本质是范围查询时新行插入导致结果集变化;MySQL在REPEATABLE READ级别通过Gap Lock锁定索引间隙防止幻读,但无索引字段、READ COMMITTED级别或普通快照读仍可能触发幻读。

幻读的本质是「范围查询 + 新行插入」
幻读不是数据被改了,而是你查的“那一片”里,突然多了一条或少了条记录。比如事务 A 执行 SELECT * FROM users WHERE age > 30 得到 5 条,事务 B 插入一条 age = 35 的新用户并提交,A 再查就变成 6 条——这多出来的那条,就是“幻影行”。关键点在于:它只发生在范围条件(>、BETWEEN、LIKE 'a%')下,精确匹配(WHERE id = 100)不会触发幻读。
MySQL 默认的 REPEATABLE READ 为什么能防幻读?
因为 InnoDB 在可重复读级别下,不只是锁住查到的行,还会用 Gap Lock(间隙锁)把查询范围的“空隙”也锁住。比如索引上有值 10、20、30,WHERE id BETWEEN 15 AND 25 不仅锁住 20 这一行,还会锁住 (10,20) 和 (20,30) 这两个间隙,阻止别人往里面插 18 或 22。但注意:间隙锁只在有索引的列上生效;全表扫描或无索引字段上的范围查询,间隙锁会退化为临键锁甚至失效。
哪些操作实际不防幻读?容易踩的坑
-
SELECT普通快照读(不加锁):即使在 RR 级别,它也只读 MVCC 快照,完全感知不到新插入的行——这不是 bug,是设计使然 - 使用
READ COMMITTED隔离级别:间隙锁被禁用,每次查询都读最新已提交版本,幻读必然发生 - 主键/唯一索引上的等值查询(如
WHERE id = ?):只会加行锁,不加间隙锁,但此时插入同 id 会因唯一约束失败,所以“幻读感”不明显;而WHERE name = ?(name 无索引)则可能直接全表扫+无间隙保护 - 显式用了
SELECT ... LOCK IN SHARE MODE却没覆盖完整范围:比如只锁了部分索引段,间隙仍可插入
真正要防幻读,得靠当前读 + 正确加锁
如果你的业务逻辑依赖“查出来没有,我就插入”,比如防重复下单,就不能只靠 SELECT 判断,必须用当前读锁定范围:
每个应用程序都要使用数据,Android应用程序也不例外,Android使用开源的、与操作系统无关的SQL数据库--SQLite,本文介绍的就是如何为你的Android应用程序创建和操作SQLite数据库。 数据库支持每个应用程序无论大小的生命线,除非你的应用程序只处理简单的数据,那么就需要一个数据库系统存储你的结构化数据,Android使用SQLite数据库,它是一个开源的、支持多操作系统的SQL数据库,在许多领域广泛使用,如Mozilla FireFox就是使用SQLite来存储配置数据的,iPhon
START TRANSACTION; SELECT * FROM orders WHERE user_id = 123 AND status = 'pending' FOR UPDATE; -- 此时不仅锁住现有行,还锁住 (prev_id, next_id) 间隙 INSERT INTO orders (...) VALUES (...); -- 安全插入,不会冲突 COMMIT;
这里 FOR UPDATE 是关键:它强制走当前读,并激活间隙锁。漏掉它,或者用错隔离级别,幻读就会在高并发下真实出现——而且往往只在压测或上线后才暴露,因为它是概率性竞争问题。









