MySQL InnoDB锁(MySQL 8 官方文档)

231 阅读8分钟

InnoDB锁

原文:dev.mysql.com/doc/refman/…

共享锁(Shared Lock,s锁)和互斥锁(Exclusive Lock,x锁)

InnoDB实现标准的行级锁,包括两种类型:共享锁互斥锁

  • 共享锁允许持有锁的事务读取一行记录
  • 互斥锁允许持有锁的事务更新删除一行记录

如果事务T1持有行r的共享锁,那么另外一个事务T2请求对行r的加锁处理方式如下:

  • 如果T2请求共享锁能马上被授予。结果就是T1和T2同时占有行r的共享锁。
  • 如果T2请求互斥锁不能马上授予,需要等待T1释放锁。

如果事务T1持有行r的互斥锁,那么另外一个事务T2请求对行r的任何加锁都不能马上被授予。事务T2必须等待事务T1释放行r的锁。

意向锁(Intention Lock)

InnoDB支持多粒度锁,即允许行级锁和表级锁共同存在。例如LOCK TABLES WRITE语句会对指定表上x锁。为了实现多粒度的锁控制,InnoDB使用意向锁。意向锁是表级别锁,表示请求一张表中的某一行的锁。有两类意向锁:

  • 意向共享锁(intention share lock,IS):一个事务意图对表的独立行设置一个共享锁
  • 意向互斥锁(intention exclusive lock,IX):一个事务意图对表的独立行设置一个互斥锁

如:SELECT FOR SHARE会对表设置一个IS锁,而SELECT FOR UPDATE会对表设置一个IX锁。

意向锁协议如下:

  • 在一个事务请求一张表的s锁之前,一定需要请求该表的的IS锁更高级别的锁
  • 在一个事务请求一张表的x锁之前,一定需要请求该表的的IX锁

表级别锁兼容性矩阵:

ISIXSX
IS兼容兼容兼容不兼容
IX兼容兼容不兼容不兼容
S兼容不兼容兼容不兼容
X不兼容不兼容不兼容不兼容

如果当前事务请求的锁和已经存在的锁兼容,那么该锁被授予;否则,不授予。当不兼容时,当前事务会等待直到不兼容的锁被释放。

除了全表请求外,意向锁不会阻塞任何请求。因为意向锁本身仅用来表示表中的某一行被锁定了。

行级锁(Record Locks)

一个行级锁会对加了索引的记录上锁。如:SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE;会阻止任何其它事务插入、更新或删除t.c1为10的记录。

即使表中没有索引,记录锁也总是锁定索引记录。在这种情况下,InnoDB会创建一个隐式的集群索引,并对该索引加记录锁。可参考Section 17.6.2.1, “Clustered and Secondary Indexes”

间隙锁(Gap Lock)

一个间隙锁会锁定一个范围的索引记录,或者锁定在第一个或最后一个索引记录之前的间隙。如:SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;会阻止其它事务插入t.c1中值为15,因为该范围的全部已经被锁定。

一个间隙可以跨越单个索引值、多个索引值,甚至是空的。

间隙锁是性能和并发性之间折衷的一部分,并且仅用于部分事务隔离级别

唯一索引

  • 唯一索引的等值查找使用行级锁,而不使用间隙锁。
  • 唯一索引的范围查找使用间隙锁

如:SELECT * FROM child WHERE id = 100;使用的是行级锁

  • 如果id没有被索引,或者有一个非唯一的索引,语句将锁定索引记录前面间隙
  • 间隙锁可以共同存在。一个事务使用的间隙锁不会阻止另一个事务在相同的间隙上使用间隙锁。共享间隙锁和独占间隙锁之间没有区别。它们彼此不冲突,并且它们执行相同的功能。例如,事务A可以在一个间隙上持有共享间隙锁(间隙s锁),而事务B在同一个间隙上持有排他性间隙锁(间隙x锁)。允许冲突的间隙锁的原因是,如果从索引中清除一条记录,则必须合并由不同事务持有的记录上的间隙锁。
  • READ COMMITTED事务隔离级别下,可以关闭间隙锁。这种情况下间隙锁对索引扫描关闭,间隙锁仅用于外键约束检查和重复键检查。
  • READ COMMITTED事务隔离级别下,在MySQL的WHERE条件没有匹配到的记录会释放行级锁。对于UPDATE语句,InnoDB使用“半一致(semi-consistent)”读,以便返回最新提交的版本,从而MySQL能够决定是否该行匹配UPDATE的WHERE条件语句。

临键锁(Next-Key Lock)

临键锁是索引的记录锁在索引记录之前的间隙的间隙锁的组合。

InnoDB执行行级锁,它扫描表索引,并对索引记录设置共享锁或排他锁。因此,行级锁实际上是索引记录锁。一条索引记录的临键锁同时会影响在索引记录之前的间隙(gap)。也就是说,临键锁是索引记录锁,加上索引记录之前的间隙的间隙锁。如果一个会话已经在记录R的一个索引上加锁,在该索引顺序下,另外一个会话不能马上在记录R插入一个新的索引记录索引记录。

假设索引包含值10,11,13和20。该索引可能的临键锁覆盖如下范围:

(negative infinity, 10]
(10, 11]
(11, 13]
(13, 20]
(20, positive infinity)

对于最后一个范围,临键锁锁定最大值以上的间隙。

默认情况下,InnoDB运行在REPEATABLE READ事务隔离级别。在这种情况下,InnoDB使用临键锁用于索引扫描,以避免幻读行(phantom rows)问题。

插入意向锁(Insert Intention Lock)

插入意向锁是INSERT操作在行插入之前设置的一种间隙锁。这个锁以这样一种方式表示插入的意图,即插入到相同索引间隙中的**多个事务如果不在间隙内的相同位置插入,则不需要彼此等待。**假设存在值为4和7的索引记录。

分别尝试插入值为5和6的事务,在获得插入行上的排他锁之前,每个事务都用插入意向锁锁住4和7之间的间隙,但不会相互阻塞,因为行不冲突。

下面的示例演示了一个事务在获得插入记录的排他锁之前使用插入意向锁。该示例涉及两个客户端A和B。

客户端A创建一个包含两个索引记录(90和102)的表,然后启动一个事务,对ID大于100的索引记录设置排他锁。排他锁包括记录102之前的间隙锁:

mysql> CREATE TABLE child (id int(11) NOT NULL, PRIMARY KEY(id)) ENGINE=InnoDB;
mysql> INSERT INTO child (id) values (90),(102);

mysql> START TRANSACTION;
mysql> SELECT * FROM child WHERE id > 100 FOR UPDATE;
+-----+
| id  |
+-----+
| 102 |
+-----+

客户端B开始一个事务,向间隙插入一条记录。事务在等待获得排他锁时,获取插入意向锁。

mysql> START TRANSACTION;
mysql> INSERT INTO child (id) VALUES (101);

SHOW ENGINE INNODB STATUS和INNODB monitor输出中,插入意向锁的事务数据如下所示:

RECORD LOCKS space id 31 page no 3 n bits 72 index `PRIMARY` of table `test`.`child`
trx id 8731 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 4; hex 80000066; asc    f;;
 1: len 6; hex 000000002215; asc     " ;;
 2: len 7; hex 9000000172011c; asc     r  ;;...

自增锁(AUTO-INC Lock)

自增锁锁是一种特殊的表级锁,用于在具有AUTO_INCREMENT列的表中插入事务。在最简单的情况下,如果一个事务正在向表中插入值,那么任何其他事务都必须等待对该表进行自己的插入,以便第一个事务插入的行接收连续的主键值。

innodb_autoinc_lock_mode变量控制用于自动增量锁定的算法。它允许您选择如何在可预测的自动递增值序列和插入操作的最大并发性之间进行权衡。

用于空间索引的谓词锁(Predicate Locks for Spatial Indexes)

InnoDB支持包含空间数据的列的空间索引。

为了处理涉及空间索引的操作的锁定,临键锁不能很好地支持REPEATABLE READSERIALIZABLE事务隔离级别。在多维数据中没有绝对排序的概念,因此不清楚哪个是“下一个”键。

为了支持带有空间索引的表的隔离级别,InnoDB使用谓词锁。空间索引包含最小边界矩形(minimum bounding rectangle, MBR)值,因此InnoDB通过为查询使用的MBR值设置谓词锁来强制对索引进行一致读取。其他事务不能插入或修改与查询条件匹配的行。