MySQL如何解决幻读?InnoDB范围锁定读的处理方法

更新时间:2026-09-08 12:53

MySQL处理幻读,重点不在于给所有SELECT加锁,而在于保护后续操作依赖的范围结果集。领取、更新或删除一批记录的事务,应在同一短事务里对索引范围执行SELECT ... FOR UPDATE,完成必要写入后提交。InnoDB会通过间隙锁和next-key锁阻止并发插入会落入该范围的新记录。

幻读和不可重复读不是一回事

幻读指同一事务按相同范围两次查询,第二次结果集多出第一次没有的新行。比如事务先查询id大于某个值的待处理订单,另一会话插入了一条也符合条件的新订单,原事务再次查询后看见了新增记录。

同一条记录的字段值被其他事务改写,是不可重复读。复现问题时保留两次SQL、事务开始与提交的位置、隔离级别和各会话的写入顺序,才能判断到底是新行进入范围,还是已有行被改动。

MySQL如何解决幻读

1、开启事务并缩短处理时间

要基于范围查询结果继续写入的业务,应先开启事务。锁定读、判断与必要写入应连续完成,提交后立即释放锁。远程调用、等待用户操作和大量计算放在事务外。

2、对业务索引范围执行FOR UPDATE

查询条件应命中实际的业务索引,例如任务状态和编号或时间组合。查询末尾加FOR UPDATE,锁定将要处理的范围,而不是先用普通SELECT查完后再分别更新。

MySQL如何解决幻读?InnoDB范围锁定读的处理方法

3、在同一事务内写入并提交

根据锁定读得到的记录更新状态、写关联记录或完成删除,然后提交事务。并发会话若要插入符合该范围的新值,会等待这段锁被释放。

InnoDB范围锁定读的处理方法

间隙锁保护两个索引记录之间的空档。锁定索引值9530与9535之间的范围后,尝试插入9533的会话会等待,直到持锁事务结束。

跨越多段索引区间的范围,可由next-key锁把记录锁和相邻间隙一起覆盖。它既约束扫描到的记录,也保护范围中的可插入位置,避免新的符合条件记录改变当前处理集合。

MySQL如何解决幻读?InnoDB范围锁定读的处理方法

索引、隔离级别和等待问题

  • 为范围条件建立合适索引。缺少索引会扩大扫描与锁定范围,也会放大等待和死锁概率。
  • 用两个会话验证:会话A执行范围FOR UPDATE,会话B插入符合该范围的新值。B应等待A提交或回滚。
  • 锁等待或死锁出现后,保存双方SQL和执行顺序,缩小范围,并让并发流程用一致顺序访问数据。
  • 只读历史数据、无需基于结果继续修改的场景使用普通一致性读取,不要无差别加范围锁。

是否使用锁定读取取决于后续业务是否要求当前结果集在事务结束前不能出现新行。把范围、索引和事务开始与提交的位置设计清楚,比盲目提高隔离级别更容易控制并发代价。