1.Shared and Exclusive Locks(共享和排他锁)
InnoDB实现了标准的行级别锁,其中有两种类型的锁,即共享的(S)锁和排他的(X)锁。
- 共享(S)锁允许保存该锁的事务读取某行。
- 排它的(X)锁允许保留该锁的事务处理更新或删除一行。
如果事务T1在第r行上保持共享(S)锁,则来自某些不同事务T2对第r行上的锁的请求处理如下:
- T2提出的S个请求锁可以立即被批准。因此,t1和t2都对r有一个S锁。
- 不能立即批准T2提出的关于X锁的请求。
如果事务T1在r行上持有排他(X)锁,则不能立即授予来自某个不同事务T2的任意类型的锁的请求。相反,事务T2必须等待事务T1释放其对第r行的锁。
2.Intention Locks(意向锁)
InnoDB支持多粒度锁定,从而允许行锁和表锁共存。例如,诸如LOCK表这样的语句…write获取指定表上的独占锁(X锁)。为了使多个粒度级别的锁定实用,InnoDB使用意向锁。意向锁定是表级锁定,它指示事务以后对表中的行需要哪种锁定类型(共享或排占)。意向锁有两种类型:
- 意向共享锁(IS)表示事务打算在表中的单个行上设置共享锁。
- 意向排他锁(IX)表示事务打算在表中的单个行上设置排他锁。
For example, SELECT … LOCK IN SHARE MODE sets an IS lock, and SELECT … FOR UPDATE
sets an IX lock.
意向锁定协议如下:
- 事务处理在获取表中行上的共享锁之前,必须先获取表上的IS锁或更强的IS锁。
- 事务在获取表中行的排他锁之前,它必须先获取表上的IX锁。
表级锁类型的兼容性总结在以下矩阵中。
X | IX | S | IS | |
---|---|---|---|---|
X | 冲突 | 冲突 | 冲突 | 冲突 |
IX | 冲突 | 共存 | 冲突 | 共存 |
S | 冲突 | 冲突 | 共存 | 共存 |
IS | 冲突 | 共存 | 共存 | 共存 |
如果请求事务与现有锁兼容,则授予该锁,但如果与现有锁冲突,则不授予该锁。事务将等待,直到有冲突的现有锁被释放。如果锁请求与现有锁冲突,并且因为会导致死锁而无法授予,则会发生错误。
意向锁定不会阻止任何内容,除了完整的表请求(例如,lock tables test write)。意图锁定的主要目的是显示某人正在锁定一行,或者要锁定表中的一行。
意图锁定的事务数据在SHOW引擎INNODB状态( SHOW ENGINE INNODB STATUS)和InnoDB监视器输出中显示如下内容类似:
TABLE LOCK table `test`.`t` trx id 10080 lock mode IX
3.Record Locks(行级锁)
记录锁定是对索引记录的锁定。例如, SELECT c1 FROM t WHERE c1 = 10 FOR
UPDATE;防止任何其他事务插入、更新或删除其中t.c1的值为10的行。
记录锁定总是锁定索引记录,即使定义的表中没有索引。对于这种情况,InnoDB将创建一个隐藏的集群索引,并使用此索引进行记录锁定。
记录锁的事务数据在SHOW引擎INNODB状态和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 ;;
4.Gap Locks(间隙锁)
间隙锁定是指对索引记录之间间隙的锁定,或在第一个索引记录之前或之后对间隙的锁定。例如,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锁)。允许冲突间隙锁的原因是,如果从索引中清除记录,则必须合并不同事务记录的记录上的间隙锁。
InnoDB中的间隙锁是“纯粹的抑制性的”,这意味着它们的唯一目的是防止其他事务插入到间隙中。间隙锁可以共存。一个事务进行的间隙锁定并不会阻止另一个事务对同一间隙进行间隙锁定。共享间隙锁和专用间隙锁之间没有区别。它们之间不冲突,而且它们执行相同的功能。
可以显式地禁用间隙锁定。如果您将事务隔离级别更改为已提交的READ或启用innodb_locks_unsafe_for_binlog系统变量(现在已弃用),则会发生这种情况。在这种情况下,间隙锁定将用于搜索和索引扫描,仅用于外键约束检查和重复键检查。
使用READ提交的隔离级别或启用innodb_locks_unsafe_for_binlog也有其他影响。在MySQL评估了where条件后,将释放不匹配行的记录锁。对于更新语句,InnoDB执行“半一致”读取,以便将最新提交的版本返回给MySQL,以便MySQL可以确定该行是否匹配更新的where条件。
5.Next-Key Locks
A next-key lock是索引记录上的记录锁定和索引记录之前的间隙锁定的组合。
InnoDB执行行级锁定的方式是,当它搜索或扫描表索引时,它会对它遇到的索引记录设置共享锁或独占锁。因此,行级锁实际上是索引记录锁。对指数记录的下一个键锁定也会影响该指数记录之前的“差距”。也就是说,下一个键锁定是索引记录锁定加上索引记录之前的间隙上的间隙锁定。如果一个会话在索引中对记录R有共享或独占锁定,则另一个会话无法在索引顺序中R之前的间隙中插入新的索引记录。
假设一个索引包含值10、11、13和20。此索引可能的下一键锁包含以下间隔,其中圆括号表示排除间隔端点,方括号表示包含端点:
(negative infinity, 10]
(10, 11]
(11, 13]
(13, 20]
(20, positive infinity)
对于最后一个间隔,下一个键锁锁定在索引中最大值上方,“最高”伪记录的值高于索引中的任何值。最高值并不是一个真正的索引记录,因此,实际上,这个下一个键锁只锁定了最大索引值之后的间隙
默认情况下,InnoDB在可重复的READ事务隔离级别中操作。在这种情况下,InnoDB使用.Next-Key Locks来进行搜索和索引扫描,从而防止幻影行
next-key lock的事务数据在 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 ;;
6.Insert Intention Locks
Insert Intention Locks是在行插入之前通过插入操作设置的间隙锁定。此锁表示有意以以下方式插入插入同一索引间隙的多个事务如果不是在同一位置插入,则不需要相互等待。假设有值为4和7的索引记录。分别尝试插入5和6值的单独事务,在获得插入行上的独占锁之前,使用插入意图锁锁定4和7之间的间隙,但不相互阻塞,因为这些行不冲突。
下面的示例演示了在获取插入记录上的独占锁之前进行插入意图锁定的事务。这个例子涉及到两个客户端,
客户端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);
Insert Intention Locks的事务数据在 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 ;;...
7.AUTO-INC Locks
AUTO-INC锁是一种特殊的表级锁,通过事务插入具有AUTO_INCREMENT列的表。在最简单的情况下,如果一个事务在表中插入值,任何其他事务都必须等待在该表中自己插入,以便第一个事务插入的行接收连续的主键值。
innodb_autoinc_lock_mode变量控制用于自动增量锁定的算法。它允许您选择如何在可预测的自动增量值序列和插入操作的最大并发性之间进行权衡。
8.Predicate Locks for Spatial Indexes
InnoDB支持对包含空间数据的列进行空间索引
要处理涉及空间索引的操作的锁定,下一个键锁定不能很好地支持可重复的READ或可序列化的事务隔离级别。在多维数据中没有绝对的排序概念,所以不清楚哪一个是“下一个”键。
为了支持具有空间索引的表的隔离级别,InnoDB使用predicate locks。空间索引包含最小的边界矩形(MBR)值,因此InnoDB通过对用于查询的MBR值设置谓词锁来强制对索引进行一致的读取。其他事务无法插入或修改与查询条件相匹配的行。