Record Lock

翻译成记录锁,是加载索引记录上的锁。

例如:select c1 from t where c1 = 10 for update 会对c1 = 10这条记录加锁,为了防止其他事务插入,更新或删除c1=10的行。

记录锁锁的是索引记录,即使表没有定义索引,InnoDB 也会创建一个隐藏的聚集索引,并使用这个索引来锁定记录。

gap Lock

翻译成间隙锁,指的是在索引记录之间的间隙上的锁。或者在第一个索引记录之前或最后一个索引之后的间隙上的锁。

  • Gap指的是InnoDB的索引数据结构中可以插入新值的问题。

    当你用select …. for update 锁定一组行时,InnoDB 可以创建锁,应用于索引中的实际值和他们的间隙。例如,当你选择所有大于10的值进行更新,间隙锁将阻止另外一个事务插入大于10的新值。

  • 既然是锁,那么就会影响到数据库的并发性。所以,间隙锁只有在RR隔离级别才起作用。

在RR隔离级别下,对于锁定的读操作(select for update, lock in share mode),update 操作,delete操作,会进行如下的加锁:

  • 对于具有唯一搜索条件的唯一索引,InnoDB只锁定找到的索引记录,不会锁定间隙。
  • 对于其他搜索条件,InnoDB会锁定扫描的索引范围,使用gap lock或next-key lock来阻塞其他事务插入范围覆盖的间隙。

    Next-key lock

    Next-key lock是索引记录上的记录锁和索引记录之间的间隙上的间隙锁的组合。

    假如一个索引包含值10,11,13,20,此索引可能的next-key锁包含以下区间: (-无穷,10] (10, 11] (11,13] (13,20] (20, 正无穷] 对于最后一个间隙,无穷并不是一个真正的索引记录,因此,实际上,这个next-key锁只锁定最大索引值之后的间隙。

  • Next-key的锁范围都是左开右闭的。

  • 只在RR隔离级别中生效。

    RR能解决幻读

    网上很多介绍,RR能解决不可重复读,但是不能解决幻读的问题,只有Serilizable能解决。其实,这种想法是不对的。
    因为Mysql跟标准的RR不一定,标准的RR确实存在幻读的问题。但是InnoDB中的RR是通过next-key lock解决了幻读的问题的。
    因为有了next-key-lock,所以需要加行锁的时候,会同时在索引的间隙中加锁,这就使得其他事务无法在这些间隙中插入记录,这就解决了幻读的问题。

    mysql的加锁原则

    两个原则:

  • 加锁的基本单位是next-key lock,是一个前开后闭区间

  • 查找的过程中访问到的对象才枷锁

优化:

  1. 索引上的等值查询,给唯一索引加锁的时候,next-key lock退化成行锁
  2. 索引上的等值查询,向右遍历时且最后一个值不满足等值查询的时候,next-key lock退化成间隙锁。

一个bug:

  1. 唯一索引上的范围查询会访问到不满足条件的第一个值为止

    举例

    数据库有以下记录:

    普通索引c 0 5 10 15 20 25

  2. 执行select id from t where c = 5 lock in share mode

    • 根据原则1,加锁的基本单位是next-key lock, 因此会给(0, 5]加上next-key lock。要注意,c是普通索引,因为仅访问c = 5 这一条记录是不能马上停下来的,需要向右遍历,查到c = 10才放弃
    • 根据原则2,访问到的数据都要加锁,因此也要给(5,10】加next-key lock
    • 根据优化2,等值判断,向右遍历,最后一个值c=5这个等值条件,因为退化成间隙锁(5,10】
    • 根据原则2,只有访问到的数据才加锁,这个查询会覆盖索引,并不需要访问主键索引,所以主键所以上没有加任何锁
  3. 执行select * from t where c >=10 and c < 11 for update

    • 根据原则1,加锁的单位是next-key lock, 会给(5,10】加next-key lock,范围查找就会往后继续找,找到id = 15这一行停下来
    • 根据原则2, 访问到的都要加锁,因此需要加next-key lock (10,15]
    • 由于索引C不是唯一索引,没有优化原则,也就是不会退化成行锁,因此最后加锁的是,索引c上的(5,10】(10,15】两个next-key lock