以 MySQL 8.0、InnoDB 为例,先区分两类读取。普通 SELECT 通常是快照读:它按事务的 ReadView 从版本链读取可见版本。在 REPEATABLE READ(RR)中,同一事务的 consistent read 通常复用第一次一致性读建立的视图,因此即使另一个事务提交了符合条件的新行,原事务重复普通查询仍看到原快照。它解决的是快照视角的一致性,不是把其他事务的写入锁住。
当前读读取当前可用的最新版本,并对读取或修改范围加锁,典型语句包括 SELECT ... FOR UPDATE、SELECT ... FOR SHARE、UPDATE 和 DELETE。在 InnoDB RR 下,对索引范围执行锁定读时通常使用 Next-Key Lock(记录锁加间隙锁)保护扫描区间,阻止并发事务插入会落入该区间的新记录,从而防止当前读场景的幻读。若按唯一索引做唯一值等值查找,可能只需记录锁;具体锁范围受索引、谓词和执行计划影响。
-- 事务 A,RRSTART TRANSACTION;SELECT * FROM orders WHERE amount >= 100; -- 快照读
-- 事务 B 插入 amount=120 并提交
-- A 再次普通 SELECT,仍读取其快照SELECT * FROM orders WHERE amount >= 100;-- A 改为当前读,会读取最新版本并锁定相应范围SELECT * FROM orders WHERE amount >= 100 FOR UPDATE;如果 A 在 B 提交后执行当前读,它可能看到新行;因此“RR 永远不会发生幻读”过于粗糙,必须说清读取类型。若业务需要“检查某范围为空后再插入”等互斥语义,应在同一事务用合适索引的锁定读保护范围,或用唯一约束等数据库约束兜底。READ COMMITTED 下普通快照读通常每条语句建立新视图,且 gap locking 行为与 RR 不同;不可把 RR 的锁范围结论直接套到其他隔离级别。
追问
快照读会阻止其他事务插入吗?
不会。普通 consistent read 通过 MVCC 读版本,不为查询范围加锁;要阻止插入需使用锁定读并确认索引和隔离级别。
SELECT ... FOR UPDATE 为什么可能锁住范围外的“行”?
锁的是索引扫描涉及的记录和间隙,范围访问可能使用 Next-Key Lock;无合适索引时扫描及锁定范围可能很大。
怎样让“账号不存在才创建”并发安全?
优先建立唯一索引并处理重复键冲突;若业务逻辑确需范围互斥,可在正确索引和隔离级别下使用锁定读,但不要仅依赖先查后插。