InnoDB可重复读隔离级别下如何避免幻读

InnoDB可重复读隔离级别下如何避免幻读

主要通过以下两种情况避免幻读

  • 表象:快照读(非阻塞读)伪MVCC
    表象避免幻读,是RR下查找数据,第一次读取创建快照,后面读取都是读取本次快照,不论别的事务是否提交相关更改,我们都不知道,掩耳盗铃
  • 内在:next-key锁(行锁+gap锁)
    上了锁,你别的操作不会修改我锁定的区间了,我就不会幻读

首先我们理解下面两个概念,当前读和快照读

  • 当前读:select xxx lock in share mode,select xxx for update、update、delete、insert
    当前读就是加了锁(共享、排他都算)的增删改查,加锁后的记录不能被别的事务修改
    InnoDB可重复读隔离级别下如何避免幻读
  • 快照读,非serializable模式下select为快照读

对主键索引或者唯一索引会用GAP锁么?

GAP区间

打开MySQL官网,我们看看它对gap区间的描述,分段进行区间划分
InnoDB可重复读隔离级别下如何避免幻读

实战验证

创建一个表,id(唯一键unique_id)、name(主键),插入数据ID为1、2、3、5、6、9,name字段随意
我手动划分一下GAP区间【1,2】【2,3】【3,5】【5,6】【6,9】【9,+∞】

  1. 如果where条件全部命中,不会使用GAP锁,只会加记录锁(行锁)
    RR模式下,S1、S2开启事务,S1查询id为9,S2插入id为9,会被blocking,插入id为7则不会锁,说明他开的是行锁,因为锁9,GAP锁会锁住【6,9】区间
    InnoDB可重复读隔离级别下如何避免幻读
  2. where条件未全部命中、全不命中会开GAP锁
    RR模式下,S1、S2开启事务,S1查询id为5、7、9(其中没有id为7的数据,部分命中),S2插入id为8,会被blocking,因为此时使用GAP锁,锁住区间【5,6】【6,9】

GAP锁会用在非唯一索引或者不走索引的当前读

  • 非唯一索引
    InnoDB可重复读隔离级别下如何避免幻读
    如上图所示,S1、S2开启事务,S1查询id=9,区间【6,9】【9,11】被锁,想执行插入id为此区间内的,都被blocking
  • 不走索引
    InnoDB可重复读隔离级别下如何避免幻读
    如图所示,id不是索引了哈!!,S1删除id=9,全表都被锁了(所有区间),尽量避免这种情况,会影响性能