本节描述了 InnoDB 使用的锁类型。
InnoDB
实现了标准的行级锁定,其中有两种类型的锁:共享 (S
) 锁 和 独占 (X
) 锁。
如果事务 T1
持有共享 (S
) 锁在行 r
上,那么来自某个不同的事务 T2
对行 r
的锁请求将被处理如下:
-
来自
T2
的共享 (S
) 锁请求可以立即被授予。因此,T1
和T2
都持有共享 (S
) 锁在r
上。 -
来自
T2
的独占 (X
) 锁请求不能立即被授予。
如果事务 T1
持有独占 (X
) 锁在行 r
上,那么来自某个不同的事务 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 可以持有共享间隙锁(gap S-lock)在间隙上,而事务 B 持有排他间隙锁(gap X-lock)在同一个间隙上。允许冲突的间隙锁的原因是,如果从索引中删除了一条记录,持有该记录的不同事务的间隙锁必须合并。
InnoDB 中的间隙锁是“纯粹抑制的”,这意味着它们的唯一目的是防止其他事务插入到间隙中。间隙锁可以共存。一个事务持有的间隙锁不阻止另一个事务持有同一个间隙的间隙锁。共享间隙锁和排他间隙锁之间没有区别,它们执行相同的功能。
间隙锁可以被明确禁用。这发生在您将事务隔离级别更改为READ COMMITTED
时。在这种情况下,间隙锁对于搜索和索引扫描被禁用,只用于外键约束检查和重复键检查。
使用READ COMMITTED
隔离级别还有其他效果。非匹配行的记录锁在MySQL评估WHERE
条件后被释放。对于UPDATE
语句,InnoDB
执行“半一致”读,以便MySQL可以确定行是否匹配WHERE
条件的UPDATE
。
Next-Key 锁是索引记录锁和该索引记录之前的间隙锁的组合。
InnoDB
以这样一种方式执行行级锁定:当它搜索或扫描表索引时,它在遇到的索引记录上设置共享或排他锁。因此,行级锁实际上是索引记录锁。Next-Key 锁也影响该索引记录之前的““间隙””。也就是说,Next-Key 锁是一个索引记录锁加上该索引记录之前的间隙锁。如果一个会话在索引中拥有记录 R
的共享或排他锁,另一个会话不能在索引顺序中 R
之前的间隙中插入新索引记录。
假设索引包含值 10、11、13 和 20。该索引的可能 Next-Key 锁涵盖以下间隔,其中圆括号表示间隔端点的排除,而方括号表示端点的包含:
(negative infinity, 10]
(10, 11]
(11, 13]
(13, 20]
(20, positive infinity)
对于最后一个间隔,Next-Key 锁锁定索引中最大值以上的间隙和一个拥有高于索引中任何值的““上限””伪记录。上限不是真正的索引记录,因此,实际上该 Next-Key 锁只锁定最大索引值后的间隙。
默认情况下,InnoDB
操作在 REPEATABLE READ
事务隔离级别下。在这种情况下,InnoDB
使用 Next-Key 锁来搜索和索引扫描,以防止幻影行(见 第 17.7.4 节,“幻影行”)。
Next-Key 锁的事务数据在 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
操作之前设置的一种间隙锁。该锁表明插入的意图,以便多个事务可以在同一个索引间隙中插入,而不需要等待对方,因为它们不是在同一个位置插入。
以下示例演示了事务在获取排他锁之前获取插入意图锁。该示例涉及两个客户端 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
列的表中插入值时获取。在最简单的情况下,如果一个事务正在将值插入表中,那么其他事务必须等待执行它们自己的插入,以便第一个事务插入的行可以获得连续的主键值。
变量 innodb_autoinc_lock_mode
控制用于自动递增锁定的算法。它允许您在可预测的自动递增值序列和插入操作的最大并发性之间进行权衡。
有关更多信息,请参阅 第 17.6.1.6 节,“InnoDB 中的 AUTO_INCREMENT 处理”。
InnoDB
支持 SPATIAL
索引的列包含空间数据(请参阅 第 13.4.9 节,“空间分析优化”)。
为了处理涉及 SPATIAL
索引的操作的锁定,next-key 锁定不适合支持 REPEATABLE READ
或 SERIALIZABLE
事务隔离级别。在多维数据中没有绝对的顺序概念,因此不清楚哪个是下一个键。
为了启用对具有 SPATIAL
索引的表的隔离级别支持,InnoDB
使用谓词锁。一个 SPATIAL
索引包含最小边界矩形(MBR)值,因此 InnoDB
通过在查询条件中使用的 MBR 值上设置谓词锁来强制一致的读取。其他事务不能插入或修改与查询条件匹配的行。