InnoDB锁定
本节介绍所使用的锁定类型InnoDB
。
- 共享锁和排他锁
- 意向锁
- 记录锁
- 间隙锁
- 下一键锁
- 插入意图锁
- 自动上锁
- 空间索引的谓词锁
共享锁和排他锁
InnoDB
实现标准的行级锁定,其中有两种类型的锁:共享(S
)锁和排他(X
)锁。
- 共享(
S
)锁允许持有锁读取行的事务。 - 一个独占(
X
)锁允许持有锁,更新或删除行的事务。
如果事务T1
持有S
对row 的共享()锁r
,则来自某些不同事务T2
的对行锁定的请求r
将按以下方式处理:
- 由A请求
T2
用于S
锁可以立即被授予。其结果是,无论是T1
与T2
持有S
的锁r
。 - 通过请求
T2
一个X
锁不能立即授予。
如果一个事务在row上T1
拥有一个独占(X
)锁r
,则不能立即批准某个不同事务T2
对任一类型的锁的请求r
。相反,事务T2
必须等待事务T1
释放对row的锁定r
。
意向锁
InnoDB
支持多种粒度锁定,允许行锁和表锁并存。例如,诸如的语句在指定表上LOCK TABLES ... WRITE
采用排他锁(X
锁)。为了使在多个粒度级别上的锁定变得切实可行,请InnoDB
使用意图锁定。意向锁是表级锁,指示事务稍后对表中的行需要哪种类型的锁(共享锁或排他锁)。有两种类型的意图锁:
- 意图共享锁(
IS
)指示一个事务打算设置一个共享上各个行锁定在表中。 - 意图独占锁(
IX
)指示一个事务打算设定各行的排他锁在表中。
例如,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 10080lock mode IX
记录锁
记录锁定是对索引记录的锁定。例如,SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE;
可以防止从插入,更新或删除行,其中的值的任何其它交易t.c1
是10
。
记录锁定始终锁定索引记录,即使没有定义索引的表也是如此。在这种情况下,请InnoDB
创建一个隐藏的聚集索引,并将该索引用于记录锁定。请参见“聚集索引和二级索引”。
用于在记录锁定事务数据出现类似于在以下SHOW ENGINE INNODB STATUS
和 InnoDB的监视器输出:
RECORDLOCKS space id 58page no 3 n bits 72index `PRIMARY `of table `test`.`t` trx id 10078 lock_mode Xlocks rec but not gap Recordlock , heapno 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;
防止其他事务将value 15
插入column中t.c1
,无论该列中是否已有这样的值,因为该范围中所有现有值之间的间隙都是锁定的。
间隙可能跨越单个索引值,多个索引值,甚至为空。
间隙锁是性能和并发性之间权衡的一部分,并且在某些事务隔离级别而非其他级别中使用。
对于使用唯一索引来锁定唯一行来锁定行的语句,不需要间隙锁定。(这不包括搜索条件仅包含多列唯一索引的某些列的情况;在这种情况下,会发生间隙锁定。)例如,如果该id
列具有唯一索引,则以下语句仅使用一个具有id
值100 的行的索引记录锁定,其他会话是否在前面的间隙中插入行并不重要:
SELECT *FROM childWHERE id = 100;
如果id
未建立索引或索引不唯一,则该语句会锁定前面的间隙。
在这里还值得注意的是,可以通过不同的事务将冲突的锁保持在间隙上。例如,事务A可以在间隙上保留一个共享的间隙锁(间隙S锁),而事务B可以在同一间隙上保留排他的间隙锁(间隙X锁)。允许冲突的间隙锁的原因是,如果从索引中清除记录,则必须合并由不同事务保留在记录上的间隙锁。
间隙锁定InnoDB
是“纯粹抑制性的”,这意味着它们的唯一目的是防止其他事务插入间隙。间隙锁可以共存。一个事务进行的间隙锁定不会阻止另一事务对相同的间隙进行间隙锁定。共享和专用间隙锁之间没有区别。它们彼此不冲突,并且执行相同的功能。
间隙锁定可以显式禁用。如果将事务隔离级别更改为,则会发生这种情况READ COMMITTED
。在这种情况下,将禁用间隙锁定来进行搜索和索引扫描,并且间隙锁定仅用于外键约束检查和重复键检查。
使用READ COMMITTED
隔离级别还有其他影响。MySQL评估WHERE
条件后,将释放不匹配行的记录锁。对于UPDATE
语句,请InnoDB
执行“半一致”读取,以便将最新的提交版本返回给MySQL,以便MySQL可以确定行是否与的WHERE
条件匹配UPDATE
。
下一键锁
下一键锁定是索引记录上的记录锁定和索引记录之前的间隙上的间隙锁定的组合。
InnoDB
执行行级锁定,以使其在搜索或扫描表索引时对遇到的索引记录设置共享或排他锁。因此,行级锁实际上是索引记录锁。索引记录上的下一键锁定也会影响该索引记录之前的“间隙”。即,下一键锁定是索引记录锁定加上索引记录之前的间隙上的间隙锁定。如果一个会话R
在索引中的记录上具有共享或排他锁,则另一会话不能R
在索引顺序之前的间隙中插入新的索引记录。
假定索引包含值10、11、13和20。此索引的可能的下一键锁定涵盖以下间隔,其中,圆括号表示排除区间端点,方括号表示包括端点:
(negative infinity, 10] (10, 11] (11, 13] (13, 20] (20, positive infinity)
对于最后一个间隔,下键锁锁定在上面的索引的最大值和间隙“确界”具有比在索引实际上任何值高的值的伪记录。最高不是真正的索引记录,因此,实际上,此下一键锁定仅锁定最大索引值之后的间隙。
默认情况下,InnoDB
以REPEATABLE READ
事务隔离级别运行。在这种情况下,请InnoDB
使用next-key锁进行搜索和索引扫描,这可以防止幻像行(请参见“幻影行(Phantom Rows)”)。
用于下一个键锁定事务数据出现类似于在下面SHOW ENGINE INNODB STATUS
和 InnoDB的监视器输出:
RECORDLOCKS space id 58page no 3 n bits 72index `PRIMARY `of table `test`.`t` trx id 10080 lock_mode X Recordlock , heapno 1 PHYSICAL RECORD: n_fields 1;compact format ; info bits 0 0: len 8; hex 73757072656d756d;asc supremum;; Recordlock , heapno 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 childWHERE id > 100FOR UPDATE ; +----- + | id | +----- + | 102 | +----- +
客户B开始交易以将记录插入空白。事务在等待获得排他锁的同时获取插入意图锁。
mysql>START TRANSACTION ; mysql>INSERT INTO child (id)VALUES (101);
用于插入意图锁定事务数据出现类似于在下面SHOW ENGINE INNODB STATUS
和 InnoDB的监视器输出:
RECORDLOCKS space id 31page no 3 n bits 72index `PRIMARY `of table `test`.`child` trx id 8731 lock_mode Xlocks gapbefore recinsert intention waiting Recordlock , heapno 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
配置选项控制用于自动增加锁定的算法。它允许您选择如何在可预测的自动增量值序列与插入操作的最大并发性之间进行权衡。
有关更多信息,请参见“ InnoDB中的AUTO_INCREMENT处理”。
空间索引的谓词锁
InnoDB
支持SPATIAL
包含空间列的列的索引(请参见“优化空间分析”)。
要处理涉及SPATIAL
索引的操作的锁定,下一键锁定不能很好地支持支持REPEATABLE READ
或SERIALIZABLE
事务隔离级别。多维数据中没有绝对排序概念,因此不清楚哪个是“下一个”键。
要支持具有SPATIAL
索引的表的隔离级别,请InnoDB
使用谓词锁。甲SPATIAL
索引包含最小外接矩形(MBR)值,因此,InnoDB
通过设置用于查询的MBR值的谓词锁强制上的索引一致的读取。其他事务不能插入或修改将匹配查询条件的行。