本章节主要讨论InnoDB所使用到的锁的类型。注:本文主要参考Mysql 5.7的官方文档,如果没有特别声明,本文提到的MySQL都是指MySQL5.7。
共享锁和排它锁
InnoDB实现标准的行级锁定,其中有两种类型的锁,共享(S)锁和独占(X)锁。
- 共享(S)锁允许持有锁的事务读取行。
- 独占(X)锁允许持有锁的事务更新或删除行。
如果事务T1在行r上持有共享(S)锁,则来自某个不同事务T2的对行r的锁的请求按如下方式处理:
- 可以立即授予T2对S锁的请求。 结果,T1和T2都在r上持有S锁。
- T2的X锁定请求不能立即授予。
如果事务T1在行r上持有独占(X)锁,则不能立即授予来自某个不同事务T2的对r上任一类型的锁的请求。 相反,事务T2必须等待事务T1释放其对行r的锁定。
意向锁(Intention Locks)
InnoDB支持多种粒度锁定,允许行锁和表锁共存。 例如,LOCK TABLES ... WRITE等语句在指定的表上采用独占锁(X锁)。 为了实现多个粒度级别的锁定,InnoDB使用意向锁定。 意向锁是表级锁,它指示事务稍后对表中的行所需的锁(共享或独占)类型。 意向锁有两种类型:
- 意向共享锁(IS)表示事务打算在表中的各个行上设置共享锁。
- 意向排他锁(IX)表示事务打算在表中的各个行上设置独占锁。
例如,SELECT ... LOCK IN SHARE MODE设置IS锁定,SELECT ... FOR UPDATE设置IX锁定。
意图锁定协议如下:
- 在事务可以获取表中某行的共享锁之前,它必须首先在表上获取IS锁或者比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
记录锁(Record Locks)
记录锁是对索引记录的锁定。 例如,SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE; 防止任何其他事务插入,更新或删除t.c1的值为10的行。
即使定义了没有索引的表,记录锁也始终锁定索引记录。 对于这种情况,InnoDB创建一个隐藏的聚簇索引并使用此索引进行记录锁定。 请参见第14.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 ;;
间隙锁(Gap Locks)
间隙锁是锁定索引记录之间的间隙,或锁定在第一个索引记录之前或最后一个索引记录之后的间隙。例如,SELECT c1 FROM t WHERE c1 BETWEEN 10 AND 20 FOR UPDATE;阻止其他事务将值15插入到列t.c1中,无论列中是否已存在这个要插入的id值,因为查询范围所有指定的边界值之间的间隔都被锁定。
间隙可以覆盖单个索引值,多个索引值,甚至可能为空。
间隙锁是性能和并发之间权衡的产物,只能用于某些事务隔离级别而不是其他级别。
使用唯一索引锁定行以搜索唯一行的语句不需要间隙锁定。 (这不包括搜索条件仅包含多列唯一索引的某些列的情况;在这种情况下,确实会发生间隙锁定。)例如,如果id列具有唯一索引,则以下语句仅使用索引记录锁定id值等于100的行,不影响其他会话是否在id值之前的间隙中插入行:
SELECT * FROM child WHERE id = 100;
如果id不是索引列或者是非唯一索引列,则该语句会锁定前一个间隙(即id<100的这个间隙)。
此处值得注意的是,冲突锁可以被不同的事务同时在间隙上持有。 例如,事务A可以在间隙上持有共享间隙锁定(间隙S锁定),而事务B在同一间隙上持有独占间隙锁定(间隙X锁定)。 允许冲突间隙锁定的原因是,如果从索引中清除记录,则必须合并由不同事务保留在记录上的间隙锁定。
InnoDB中的间隙锁是“纯粹的抑制”,这意味着它们的唯一目的是防止其他事务插入间隙。 间隙锁可以共存。 一个事务占用的间隙锁定不会阻止另一个事务在同一个间隙上进行间隙锁定。 共享和独占间隙锁之间没有区别。 它们彼此不冲突,它们执行相同的功能。
可以明确禁用间隙锁定。 如果将事务隔离级别更改为READ COMMITTED或启用innodb_locks_unsafe_for_binlog系统变量(现已弃用),则会发生这种情况。 在这些情况下,对于搜索和索引扫描禁用间隙锁定,并且仅用于外键约束检查和重复键检查。
使用READ COMMITTED隔离级别或启用innodb_locks_unsafe_for_binlog还有其他影响。 MySQL评估WHERE条件后,将释放不匹配行的记录锁。 对于UPDATE语句,InnoDB执行“半一致”读取,以便将最新提交的版本返回给MySQL,以便MySQL可以确定该行是否与UPDATE的WHERE条件匹配。
Next-key Locks
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锁将间隙锁定在索引中最大值之上,而“supremum”伪记录的值高于索引中实际的任何值。 supremum不是真正的索引记录,因此,实际上,此next-key锁仅锁定最大索引值之后的间隙。
默认情况下,InnoDB在REPEATABLE READ事务隔离级别运行。 在这种情况下,InnoDB使用next-key锁进行搜索和索引扫描,从而防止幻像行。
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 Intention Locks)
插入意向锁是在行插入之前由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 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 Locks)
AUTO-INC锁是由插入到具有AUTO_INCREMENT列的表中的事务所采用的特殊表级锁。 在最简单的情况下,如果一个事务正在向表中插入记录,则任何其他事务必须先等待,再对该表执行自己的插入,以便第一个事务插入的行接收连续的主键值。
innodb_autoinc_lock_mode配置选项控制用于自动增量锁定的算法。 它允许您选择如何在可预测的自动增量值序列和插入操作的最大并发之间进行权衡。
Predicate Locks for Spatial Indexes
(暂未接触过空间列相关的数据库概念,日后补充)
网友评论