原文地址: CoderGO : 数据库中的锁与事务
译自MySQL 5.7 Reference Manual:15.3.1 InnoDB Locking
本章了在InnoDB中使用的锁类型
InnoDB实现了标准的行级锁,有两种类型:共享锁(S锁)和排它锁(X锁)。
如果事务T1持有行记录r的上一个共享(S)锁,接着另一个事务T2请求行记录r上的锁,数据库处理如下:
如果事务T1持有r上的排它(S)锁,事务T2请求任意一个锁都不立刻获得。此时,T2必须等待T1释放r上的锁。
InnoDB支持多粒度的锁,多粒度的锁允许行锁和表锁共存。为了在实际中实现多粒度的锁,使用了另外一种类型的锁:意向锁。在InnoDB中,意向锁是表级锁,它标明了一个事务在将在表中行记录上使用锁的类型(共享锁和排它锁)。在InnoDB中使用了两种意向锁(假设事务T请求了表t上的锁)
意向锁的协议如下:
这些规则可以总结为以下的锁兼容矩阵:
X | IX | S | IS |
---|---|---|---|
X | 冲突 | 冲突 | 冲突 |
IX | 冲突 | 兼容 | 冲突 |
S | 冲突 | 冲突 | 兼容 |
IS | 冲突 | 兼容 | 兼容 |
如果一个事务请求的锁和已经存在的锁兼容,则此事务会得到锁,否则不能得到锁。事务会等待直到已有的冲突锁被释放。如果一个锁请求和已有的锁发生冲突,则它不会得到锁,因为会发生死锁并引发错误。
因此,意向锁不会锁住任何记录除非有全表锁请求(例如,LOCK TABLES … WRITE)。IX锁和IS锁的主要目的是显示有人锁定了一行记录或者准备锁定一行记录。
记录锁是索引记录上的锁。例如,SELECT c1 FOR UPDATE FROM t WHERE c1 = 10;
可以防止任何其它事务插入、 更新或删除t.c1等于10的行。
记录锁锁定的是索引,即使表没有定义索引。这种情况下,InnoDB会创建一个隐式的聚簇索引并将此索引用作记录锁。
间隙锁会锁定一个范围内的索引,或者是某索引记录之前或者之后的索引。例如,SELECT c1 FOR UPDATE FROM t WHERE c1 BETWEEN 10 and 20;
可以防止其他事务将一个t.c1等于15的值插入到表中,无论列中是否有该记录,因为该范围内所有可能存在的值都被锁定。
间隙有可能跨越单索引值,多个索引值,甚至空值。
间隙锁是的性能和并发性之间的这种的部分结果,并被用于某写事务的隔离级别而不其他级别。
在使用唯一索引查找唯一行时,锁定查找的行时不不要间隙锁。(这不包括查找条件中,条件列只是多列唯一索引中的部分时;此时,间隙锁定会发生)。例如,例如,如果id列具有唯一索引,下面的语句仅使用索引记录(记录锁)锁定id等于100的行,对其他会话在此行前面的间隙插入行不会影响。
SELECT * FROM child WHERE id = 100;
如果id没有位于索引上或有一个非唯一索引,那么上述语句会锁住它之前的间隙。
值得注意的是在一个间隙上不同的事务可以持有冲突的锁。例如,事务A可以持有一个间隙上的共享间隙锁(gap S-lock),同时事务B可以持有该间隙上的排它间隙锁(gap X-lock)。允许冲突的间隙锁存在原因是如果一个记录从索引上清除,不同事务上持有该记录的间隙锁必须被合并。
在InnoDB中,间隙锁是“完全被抑制”的,意思是它只阻止其它事务给往间隙中插入。它们不阻止其他事务在同一个间隙上获得间隙锁。因此,一个间隙X锁和一个间隙S锁效果一样。
可以显式禁用间隙锁。如果你更改事务隔离级别为READ COMMITTED或启用innodb_locks_unsafe_for_binlog系统变量 (现已废弃),禁用会生效。在这些情况下,间隙锁在查找和索引扫描时会被禁用,仅在外键约束检查和重复键检查时启用。
使用READ COMMITTED隔离级别或者启用innodb_locks_unsafe_for_binlog还有其他副作用。没有匹配行的记录锁在MySQL评估完WHERE
条件后会释放掉。对于UPDATE
语句,InnoDB做了“半一致”读,因此它将返回最后一个提交的版本给MySQL,这样MySQL可以判断这行数据是否和UPDATE
的WHERE
条件匹配。
Next-Key锁是索引记录上的记录锁与此索引记录之前间隙上的间隙锁两者的结合。InnoDB在查找或扫描索引时使用行级锁,给遇到的索引记录上设置共享锁或者排它锁。因此,行级锁就是索引记录锁。一个索引记录上的next-key锁也影响在该索引之间的“间隙”。也就是说,next-key是一个索引记录锁加上在该索引记录之前间隙上的间隙锁。如果一个会话在记录R的索引上持有一个共享锁或者排它锁,另一个会话无法在在R的索引之前立刻插入一个新的索引记录。
假设一个索引包括10,11,13和20几个值。next-key锁有可能包括如下的区间,其中()表示开区间,[]表示闭区间。
(-∞, 10]
(10, 11]
(11, 13]
(13, 20]
(20, +∞)
对于最后一个区间,next-key锁锁定了大于最大值的的所有索引,上界记录是一个大于任何已有记录的伪记录。上界并不是一个真正的索引记录,因此,next-key锁只锁定了最大值以上的值。
默认情况下,InnoDB工作在REPEATABLE READ事务隔离级别。此时,InnoDB使用next-key锁查找和扫描索引,阻止了幻行。
插入意向锁时在插入行前设置的一种间隙锁。这个锁示意如果多个事务感兴趣的不是索引区间中的同一个位置,则事务在同一个索引区间插入不需要相互等待。假设有索引记录值为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
。数据在标题为TRANSACTIONS的下面:
mysql> SHOW ENGINE INNODB STATUS\G
...
SHOW ENGINE INNODB STATUS
---TRANSACTION 8731, ACTIVE 7 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 360, 1 row lock(s)
MySQL thread id 3, OS thread handle 0x7f996beac700, query id 30 localhost root update
INSERT INTO child (id) VALUES (101)
------- TRX HAS BEEN WAITING 7 SEC FOR THIS LOCK TO BE GRANTED:
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_increment
列的表插入数据。在最简单的情况下,如果一个事务正在给表中插入数据,其他的事务想要插入数据,必须等待,这样第一个事务才能插入具有连续主键的值。
innodb_autoinc_lock_mode
配置选项控制用于自动增长锁的算法。它允许你选择如何权衡可预测的自动增长值序列和最大的插入操作并发性。
更多的信息,请参阅第 15.6.5 节,“InnoDB中的AUTO_INCREMENT处理”。
在MySQL 5.7.5,InnoDB支持包含空间数据列的空间索引(见第12.5.3.5条,“优化空间数据分析”)。
处理包含空间索引的锁,next-locking锁不能很好的支持REPEATABLE READ或SERIALIZABLE事务隔离级别。因为在多维数据中,没有绝对的顺序概念,因此“next”key不明确。
为了使得空间索引支持隔离级别,InoDB使用预测锁。一个空间索引包含最小边界矩形 (MBR)值,所以InnoDB通过在MBR上设置预测锁来查询数据,强制一致性读。其它事务不能插入或修改与查询条件匹配的行。
注:MySQL官方手册里面关于InnoDB锁类型这一节讲的比较透彻,索引将其全部翻译,可以帮我改正。