本节介绍 InnoDB
使用的锁类型。
InnoDB
实现标准的 行级锁定,其中有两种类型的锁,共享锁 (S
) 锁 和 排他锁 (X
) 锁。
如果事务 T1
对行 r
持有共享锁 (S
) 锁,则来自不同事务 T2
对行 r
的锁请求将按以下方式处理
T2
对S
锁的请求可以立即被授予。因此,T1
和T2
都对r
持有S
锁。T2
对X
锁的请求无法立即被授予。
如果事务 T1
对行 r
持有排他锁 (X
) 锁,则来自不同事务 T2
对 r
的任何类型锁的请求都无法立即被授予。相反,事务 T2
必须等待事务 T1
释放其对行 r
的锁。
InnoDB
支持 多粒度锁定,允许行锁和表锁共存。例如,语句 LOCK TABLES ... WRITE
对指定的表获取排他锁(X
锁)。为了使多粒度级别的锁定变得实用,InnoDB
使用 意向锁。意向锁是表级锁,它指示事务稍后需要为表中的行设置哪种类型的锁(共享或排他)。有两种类型的意向锁
例如,SELECT ... FOR SHARE
设置 IS
锁,而 SELECT ... FOR UPDATE
设置 IX
锁。
意向锁定协议如下
在事务可以获取表中行的共享锁之前,它必须首先获取表上的
IS
锁或更强的锁。在事务可以获取表中行的排他锁之前,它必须首先获取表上的
IX
锁。
表级锁类型兼容性在以下矩阵中总结。
X |
IX |
S |
IS |
|
---|---|---|---|---|
X |
冲突 | 冲突 | 冲突 | 冲突 |
IX |
冲突 | 兼容 | 冲突 | 兼容 |
S |
冲突 | 冲突 | 兼容 | 兼容 |
IS |
冲突 | 兼容 | 兼容 | 兼容 |
如果请求事务与现有锁兼容,则会授予锁,但如果与现有锁冲突,则不会授予锁。事务将等待冲突的现有锁被释放。如果锁请求与现有锁冲突并且由于会造成死锁而无法授予,则会发生错误。
意图锁不会阻止任何操作,除了完整表请求(例如,LOCK TABLES ... WRITE
)。意图锁的主要目的是表明有人正在锁定一行,或者将要锁定表中的某一行。
意图锁的事务数据在SHOW ENGINE INNODB STATUS
和 InnoDB 监控器 输出中显示类似以下内容
TABLE LOCK table `test`.`t` trx id 10080 lock mode IX
记录锁是对索引记录的锁定。例如,SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE;
会阻止任何其他事务插入、更新或删除 t.c1
值为 10
的行。
记录锁始终锁定索引记录,即使表定义为没有索引。对于此类情况,InnoDB
会创建一个隐藏的聚集索引,并使用此索引进行记录锁定。参见 第 17.6.2.1 节,“聚集索引和二级索引”。
记录锁的事务数据在SHOW ENGINE INNODB STATUS
和 InnoDB 监控器 输出中显示类似以下内容
RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
trx id 10078 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 8000000a; asc ;;
1: len 6; hex 00000000274f; asc 'O;;
2: len 7; hex b60000019d0110; asc ;;
间隙锁是对索引记录之间的间隙的锁定,或对第一个索引记录之前或最后一个索引记录之后的间隙的锁定。例如,SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;
会阻止其他事务将 15
的值插入到 t.c1
列中,无论该列中是否已经存在该值,因为该范围内所有现有值的间隙都被锁定。
间隙可能跨越单个索引值、多个索引值,甚至可能是空的。
间隙锁是性能和并发性之间权衡的一部分,在一些事务隔离级别中使用,而在其他隔离级别中不使用。
对于使用唯一索引来搜索唯一行的语句,不需要间隙锁。(这并不包括搜索条件仅包含多列唯一索引中某些列的情况;在这种情况下,会发生间隙锁定。)例如,如果 id
列具有唯一索引,则以下语句仅对具有 id
值 100 的行使用索引记录锁,并且其他会话是否在前面的间隙中插入行并不重要
SELECT * FROM child WHERE id = 100;
如果 id
没有索引或具有非唯一索引,则该语句会锁定前面的间隙。
这里还值得注意的是,不同事务可能会在间隙上持有冲突锁。例如,事务 A 可以在一个间隙上持有共享间隙锁(间隙 S-锁),而事务 B 在同一个间隙上持有排他间隙锁(间隙 X-锁)。允许冲突间隙锁的原因是,如果从索引中清除了一条记录,则不同事务在该记录上持有的间隙锁必须合并。
InnoDB
中的间隙锁是 “纯粹的禁止性”,这意味着它们的唯一目的是防止其他事务插入到间隙中。间隙锁可以共存。一个事务获得的间隙锁不会阻止另一个事务在同一个间隙上获得间隙锁。共享间隙锁和排他间隙锁之间没有区别。它们不会彼此冲突,并且执行相同的功能。
间隙锁可以显式禁用。如果您将事务隔离级别更改为 READ COMMITTED
,就会发生这种情况。在这种情况下,间隙锁被禁用以进行搜索和索引扫描,并且仅用于外键约束检查和重复键检查。
使用 READ COMMITTED
隔离级别还会产生其他影响。对于不匹配行的记录锁会在 MySQL 评估 WHERE
条件后被释放。对于 UPDATE
语句,InnoDB
会执行 “半一致性” 读取,这样它会将最新提交的版本返回给 MySQL,以便 MySQL 可以确定该行是否匹配 UPDATE
的 WHERE
条件。
下一个键锁是索引记录上的记录锁和索引记录之前的间隙上的间隙锁的组合。
InnoDB
执行行级锁定,以便当它搜索或扫描表索引时,它会在遇到的索引记录上设置共享锁或排他锁。因此,行级锁实际上是索引记录锁。索引记录上的下一个键锁也会影响该索引记录之前的 “间隙”。也就是说,下一个键锁是索引记录锁加上索引记录之前的间隙上的间隙锁。如果一个会话对索引中的记录 R
持有共享锁或排他锁,则另一个会话不能在索引顺序中 R
之前的间隙中插入新的索引记录。
假设索引包含值 10、11、13 和 20。此索引的可能的下一个键锁涵盖以下间隔,其中圆括号表示排除间隔端点,方括号表示包含间隔端点
(negative infinity, 10]
(10, 11]
(11, 13]
(13, 20]
(20, positive infinity)
对于最后一个间隔,下一个键锁会锁定索引中最大值上方的间隙和具有大于索引中任何值的 “上确界” 伪记录。上确界不是真正的索引记录,因此,实际上,此下一个键锁仅锁定最大索引值后面的间隙。
默认情况下,InnoDB
在 REPEATABLE READ
事务隔离级别下运行。在这种情况下,InnoDB
使用下一个键锁进行搜索和索引扫描,这可以防止幻影行(参见 第 17.7.4 节,“幻影行”)。
下一个键锁的事务数据在SHOW ENGINE INNODB STATUS
和 InnoDB 监控器 输出中显示类似以下内容
RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
trx id 10080 lock_mode X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 8000000a; asc ;;
1: len 6; hex 00000000274f; asc 'O;;
2: len 7; hex b60000019d0110; asc ;;
插入意图锁是由 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 监控器 输出中显示类似以下内容
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 锁是由插入具有 AUTO_INCREMENT 列的表的 AUTO_INCREMENT
的事务获取的一种特殊的表级锁。在最简单的情况下,如果一个事务正在插入表中的值,则任何其他事务都必须等待才能执行自己的插入操作,以便第一个事务插入的行获得连续的主键值。
innodb_autoinc_lock_mode
变量控制用于自动增量锁定的算法。它允许您选择如何权衡自动增量值的可预测序列和插入操作的最大并发性。
有关更多信息,请参见 第 17.6.1.6 节,“InnoDB 中的 AUTO_INCREMENT 处理”。
InnoDB
支持包含空间数据的列的 SPATIAL
索引(参见 第 13.4.9 节,“优化空间分析”)。
为了处理涉及 SPATIAL
索引的操作的锁定,下一个键锁定不太适合支持 REPEATABLE READ
或 SERIALIZABLE
事务隔离级别。多维数据中没有绝对的排序概念,因此不清楚哪个是 “下一个” 键。
为了启用对具有 SPATIAL
索引的表的隔离级别的支持,InnoDB
使用谓词锁。一个 SPATIAL
索引包含最小外接矩形 (MBR) 值,因此 InnoDB
通过在用于查询的 MBR 值上设置谓词锁来强制执行索引上的一致读取。其他事务不能插入或修改与查询条件匹配的行。