mysql Innodb 中的三种锁算法

纯干货,更有深度的讲讲mysql中的三种锁算法(临建锁、间隙锁、行锁)。

前言

先了解锁要解决的问题:事务隔离

mysql innodb 引擎,为了降低读写冲突,采用了 MVCC 多版本并发控制协议。MVCC 最大的好处是 读不加锁,读写不冲突,降低冲突意味着并发性能的提升。

MVCC 在并发控制中读操作可分为两种:当前读快照读 ,快照读读取的是记录的当前可见版本,可能是历史版本,不用加锁。当前读,读取的是记录的最新版本,并且当前返回的记录均会加锁,保证其他事务对其数据不可更改。

  • 快照读:简单的select操作,属于快照读,不加锁。(有例外)

    select * from table where ?;

  • 当前读:特殊的读操作,插入/更新/删除操作,属于当前读,需要加锁。  

    select * from table where ? lock in share mode; S锁 (共享锁)

    select * from table where ? for update;

    insert into table values (…);

    update table set ? where ?;

    delete from table where ?;

记录锁 RECORD_LOCK

行锁是基于索引的,在索引上加锁,非记录本身上加锁,若该记录没有任何索引,那么innodb会在后台创建一个隐藏的聚集主键上加锁,锁的是该隐藏聚集主键索引。所以当sql没有任何索引的时候会将所有聚集索引后都加 X 锁,这个就是类似表锁(但不是表锁)。

为什么所有记录都加 X 锁呢,是因为当无法通过索引快速扫描到数据时,引擎层面会将所有记录加锁返回,然后由mysql server 层进行过滤,但是,为了效率考量,MySQL做了优化,对于不满足条件的记录,会在判断后放锁,最终持有的,是满足条件的记录上的锁,但是不满足条件的记录上的加锁/放锁动作不会省略。

间隙锁 GAP LOCK

间隙锁使用前提

  • 间隙锁只在 RR 隔离级别时使用,主要用来防止幻读的情况。
  • sql走的索引为非唯一索引,(唯一索引只有锁住多条记录或者一条不存在的记录的时候,才会产生间隙锁,指定给某条存在的记录加锁的时候,只会加记录锁,不会产生间隙锁)。
  • 当sql是一个范围操作,即使不是非唯一索引也可以用间隙锁。

间隙的范围

根据检索条件向下寻找最靠近检索条件的记录值A作为左区间,向上寻找最靠近检索条件的记录值B作为右区间,即锁定的间隙为(A,B)

UPDATE和DELETE时,除了对唯一索引的唯一搜索外都会获取gap锁或next-key锁。即锁住其扫描的范围。如果使用相等条件请求给一个不存在的记录加锁,InnoDB也会使用间隙锁。

临建锁 NEXT-KEY LOCK

临建锁依然是 RR 级别才可使用,临键锁只与非唯一索引列有关,在唯一索引列(包括主键列)上不存在临键锁。所谓Next-Key Locks,就是Record lock和gap lock的结合,即除了锁住记录本身,还要再锁住索引之间的间隙。

间隙锁锁定的是一段左开右闭的索引区间。(1,10]

临建锁退化为间隙锁的条件为,当搜素的数据不存在时,临建锁会退化为间隙锁(因为间隙锁只会锁间隙,可以省去通过记录锁锁定存在的数据)。

InnoDB 选择临键锁作为行锁的默认算法,然后再进行锁退化依次选择间隙锁、行锁进行锁定,这么做的原因是降低锁的粒度,减少锁冲突。