MySQL InnoDB 中的锁

Posted by icoding168 on 2020-04-12 16:32:47

分类: MySQL  

MySQL InnoDB 存储引擎使用了以下几种类型的锁:

  • 共享锁和排他锁
  • 意向锁
  • 记录锁
  • 间隙锁
  • Next-Key 锁
  • 插入意向锁
  • 自增锁
  • 空间索引断言锁

共享锁和排他锁

InnoDB 实现了两种标准的行级锁:共享锁(简称 s 锁)和排他锁(简称 x 锁)。

  • 共享锁允许持锁事务读取一行
  • 排它锁允许持锁事务更新或者删除一行

如果事务 T1 持有行 r 的 s 锁,那么另一个事务 T2 请求 r 的锁时,会做如下处理:

  • T2 请求 s 锁会立即成功获取,T1 和 T2 都持有 r 行的 s 锁
  • T2 请求 x 锁不能立即成功获取,需要等待 T1 释放 s 锁

如果事务 T1 持有行 r 的 x 锁,那么对另一个事务 T2 来说,无论是 s 锁还是 x 锁,T2 都必须等待 T1 释放 x 锁才能获取。

意向锁

InnoDB 支持多粒度的锁,允许表级锁和行级锁共存。举个例子,LOCK TABLES 语句可以用来对某个表加排他锁。为了实现多粒度锁,InnoDB 使用了意向锁(简称 I 锁)。意向锁是一种表级锁,用来在事务中对某一行数据显式指定用共享锁还是排他锁。

  • 意向共享锁(简称 IS 锁)表明一个事务意图在某个表中设置单行的共享锁
  • 意向排它锁(简称 IX 锁)表明一个事务意图在某个表中设置单行的排他锁

例如, SELECT … LOCK IN SHARE MODE 设置一个 IS 锁,SELECT … FOR UPDATE 设置一个 IX 锁。

意向锁有以下规定:

  • 一个事务必须先持有该表上的 IS 锁或者更强的锁才能持有该表中某行的 S 锁
  • 一个事务必须先持有该表上的 IX 锁才能持有该表中某行的 X 锁

各类型锁的兼容性表格:

在一个事务中,新的锁只有兼容已有的锁才能成功获取,否则必须等待已有的锁被释放,这样做是为了避免死锁的发生。

除了全表请求语句,例如 LOCK TABLES 语句,意向锁不会阻塞任何东西。意向锁的主要目的是表示某人正在锁定表中一行,或者将要锁定一行。

记录锁

记录锁是索引记录上的锁,举个例子, SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE 会阻止其他所有事务插入、修改或者删除 t.c1 是 10 的行。

记录锁总是锁定索引记录,即使表没有索引,因为 InnoDB 会创建隐式的索引,并使用这个索引实施记录锁。

间隙锁

间隙锁(gap)是索引记录之间的间隙上的锁,或者说第一个索引记录之前或最后一个索引记录之后的间隔上的锁,间隙不包括首尾两个索引记录。例如 SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE 会阻止其他事务插入 t.c1 = 15 的记录,即使表中没有 c1 = 15 的记录,所有处于筛选范围内的行也会被上锁。

一个间隙可能是一个索引值、多个索引值,甚至是空的。

间隙锁是在性能与并发之间的一种折衷选择,并且只适用于一些事务隔离级别。

使用唯一索引的时候用不上间隙锁。例如,id 列有唯一索引,下面的语句只是用索引记录锁(针对id=100的行)不管其他会话是否在前面的间隙中插入行。

SELECT * FROM child WHERE id = 100;

如果 id 列没有索引或者 id 列的索引不是唯一索引,那么这条语句的确会锁住前面的间隙。

不同的事务可以在同一个间隙中持有互相冲突的锁,例如在同一个间隙中,事务 A 可以持有一个共享的间隙锁(gap S-lock),同时事务 B 持有排他的间隙锁(gap X-lock)。

间隙锁可以被显式的关闭,比如设置事务隔离级别为 Repeatable Commited。

Next-Key 锁

Next-Key Locks 是记录锁和间隙锁的组合。

InnoDB 会在搜索或扫描表索引时,对遇到的索引记录设置共享锁或排他锁,因此行级锁实际上就是索引记录锁。索引记录上的 Next-Key 锁也会影响该索引记录之前的“ 间隙 ”。也就是说,Next-Key 锁是由索引记录锁加上索引记录之前的间隙上的间隙锁组合而成。如果一个会话在索引中的某个记录 R 上具有共享锁或排他锁 ,则另一会话不能马上在间隙中插入一个排在 R 前面的新索引记录。

默认情况下,Innodb 是 Repeatable Read 隔离级别,Innodb 会使用 Next-Key 锁来进行索引搜索和扫描,阻止了幻读的发生。

插入意向锁

插入意向锁是在插入一条记录行前,由 insert 操作产生的一种间隙锁。该锁用以表示插入意向,当多个事务在同一间隙插入位置不同的多条数据时,事务之间不需要互相等待。假设存在两条值分别为 4 和 7 的记录,两个不同的事务分别试图插入值为 5 和 6 的两条记录,每个事务在获取排他锁前,都会获取(4,7)之间的间隙锁,但是因为数据行之间并不冲突,所以两个事务之间并不会产生阻塞等待。

虽然插入意向锁中含有意向锁三个字,但是它并不属于意向锁而属于间隙锁,因为意向锁是表锁而插入意向锁是行锁。