17.7.1 InnoDB 锁
本节描述了InnoDB
使用的锁类型。
InnoDB
实现了标准的行级锁,其中有两个类型的锁,共享(S
)锁和排他(X
)锁。
如果事务T1
持有共享(S
) 锁在行r
上,然后来自一些distinct的事务T2
对行r
请求锁将被处理如下:
-
T2
对共享(S
) 锁的请求可以立即授予。结果,T1
和T2
都持有r
上的共享(S
) 锁。 -
T2
对独占(X
) 锁的请求不能立即授予。
如果事务T1
持有独占(X
) 锁在行r
上,然后来自一些distinct的事务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
)。意向锁的主要目的是显示someone正在锁行或将要锁表中的行。
意向锁的事务数据在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 间隙
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;
防止其他事务插入t.c1值为15
的行,无论该值是否已经存在于该列中,因为间隙锁了所有在范围内的间隙。
间隙可能跨越单个索引值、多个索引值或为空。
间隙锁是性能和并发性之间的一种权衡,用于某些事务隔离级别中,而不是其他级别中。
间隙锁不需要用于使用唯一索引锁行的语句,以搜索唯一行。 (这不包括多列唯一索引中只包含一些列的情况;在这种情况下,间隙锁确实会发生。) 例如,如果id
列有唯一索引,那么以下语句使用的是对具有id
值为100的行的索引记录锁,并且不管其他会话在前一个间隙中插入行是否发生。
SELECT * FROM child WHERE id = 100;
如果id
没有索引或有非唯一索引,那么语句确实锁了前一个间隙。
此外,需要注意的是不同的事务可以持有同一个间隙上的冲突锁。例如,事务A可以持有共享间隙锁(间隙 S-锁)在间隙上,而事务B则持有该间隙上的排他间隙锁(间隙 X-锁)。允许冲突间隙锁的原因是,如果记录从索引中被purged,持有该记录的不同事务的间隙锁必须被合并。
InnoDB中的间隙锁是“纯粹的抑制性”,这意味着它们唯一的目的是防止其他事务插入到间隙中。间隙锁可以共存。一个事务持有的间隙锁不禁止另一个事务在同一个间隙上持有间隙锁。没有区别于共享和排他间隙锁。它们之间不冲突,并且执行相同的函数。
间隙锁可以被禁用。这种情况发生在你将事务隔离级别更改为READ COMMITTED
时。在这种情况下,间隙锁用于搜索和索引扫描中被禁用,只用于外键约束检查和重复键检查。
使用READ COMMITTED
隔离级别还会产生其他影响。对于不匹配行的记录锁,MySQL 在评估WHERE
条件后释放这些锁。对于UPDATE
语句,InnoDB
执行“semi-consistent”读取,以便返回最新提交的版本给MySQL,以便MySQL确定行是否匹配WHERE
条件的UPDATE
。
临键锁是一种组合锁,包括索引记录的记录锁和前一个索引记录的间隙锁。
InnoDB
在搜索或扫描表索引时,以共享或独占锁的方式锁索引记录。因此,这些行级锁实际上是索引记录锁。对索引记录的临键锁也会影响前一个索引记录的间隙。换言之,临键锁是一个索引记录锁加上间隙锁在间隙之前的索引记录。如果一个会话对索引记录R
持有共享或独占锁,另一个会话不能在索引顺序中插入新索引记录在R
之前的间隙。
假设索引包含值10、11、13和20。对于这个索引,可能的临键锁将覆盖以下间隔,其中圆括号表示排除端点,方括号表示包括端点:
(negative infinity, 10]
(10, 11]
(11, 13]
(13, 20]
(20, positive infinity)
最后一个间隔的临键锁索引中的最大值上方的间隙,以及具有大于索引中实际值的“supremum”伪记录。supremum不是真正的索引记录,因此实际上,这个临键锁只锁索引中最大的值后的间隙。
默认情况下,InnoDB
在REPEATABLE READ
事务隔离级别下运行。在这种情况下,InnoDB
使用next-key锁来搜索和索引扫描,这可以防止幻影行(见第17.7.4节,“Phantom Rows”)。
临键锁的事务数据在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,每个事务在插入行之前锁间隙,但不会阻塞对方,因为这两个行不冲突。
以下示例演示了一个事务在插入记录之前获取插入意图锁的过程。该示例涉及到两个客户端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 间隙 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_INCREMENT
列时,会获取一个特殊的表级锁AUTO-INC
。在最简单的情况下,如果一个事务正在将值插入到表中,那么任何其他事务都必须等待,以便将由第一个事务插入的行分配给连续的主键值。
innodb_autoinc_lock_mode
变量控制了AUTO-INCREMENT锁的算法。它允许您选择在插入操作中之间的平衡预测的自增值序列和并发性。
空间索引的谓词锁
InnoDB
支持对包含空间数据的列进行SPATIAL
索引(见第13.4.9节,“优化空间分析”)。
为了处理涉及SPATIAL
索引的操作,临键锁不太适合支持REPEATABLE READ
或SERIALIZABLE
事务隔离级别。由于多维数据没有绝对的顺序概念,所以不知道哪个是““next””键。
为了支持隔离级别的表格,其中包含SPATIAL
索引,InnoDB
使用谓词锁。一个SPATIAL
索引包含最小边界矩形(MBR)值,所以InnoDB
通过将用于查询的 MBR 值设置为谓词锁来强制一致读取索引。其他事务不能插入或修改一个与查询条件匹配的行。