官方文档描述InnoDB使用的锁的八个分类
共享锁(Shared Locks)和排它锁(Exclusive Locks)
InnoDB实现了两种标准的行级别锁, 分别是共享锁(又称S锁)和排它锁(又称X锁)。
- 一个共享锁允许持有它的事务去读取某行数据。
- 一个排它锁允许持有它的事务去更新或删除某行数据。
如果事务T1持有数据行r的共享锁, 后续其它不同事务T2关于行r上锁的请求会按照如下规则处理:
- T2事务获取S锁的请求会直接成功, 此时T1和T2事务都各自持有一个数据行r的共享锁。
- T2事务获取X锁的请求不会立即成功。
如果事务T1持有数据行r的排它锁, 其它事务T2获取数据行r的共享锁或者排它锁都不会成功, T2只能等待T1释放它在行r上持有的锁。
意向锁(Intention Locks)
InnoDB支持多种粒度锁, 允许行锁和表锁共存。 举例来说, 类似"LOCK TABLES ... WRITE"会持有指定表的排它锁(X锁)。 InnoDB使用意向锁来切实有效地实现多粒度级别的锁。 意向锁是表级别的锁, 用于表明事务后续会对该表某行数据获取哪个类型(共享或排它)的锁, 有两种意向锁:
- 意向共享锁(Intention Shared Lock, IS)表明事务准备在表上某行设置一个共享锁。
- 意向排它锁(Intention Exclusive Lock, IX)表明事务准备在表上某行设置一个排它锁。
例如"SELECT ... LOCK IN SHARE MODE"会设置一个意向共享锁, "SELECT ... FOR UPDATE"会设置一个意向排它锁。
意向锁的规则如下:
- 事务在获取表上某行数据的共享锁之前, 必须先在该表获取一个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;"会阻止所有其它事务插入一条c1列值为15的数据到表中, 无论当前表是否已经存在c1列值为15的数据, 因为指定范围10~20内的所有间隙都会被锁住。
间隙锁可能跨越单个索引值、多个索引值, 甚至可能是空集。
间隙锁是性能和并发之间妥协的产物, 只在某些事务隔离级别中使用。
对于通过唯一索引查询单一数据行的语句不需要使用间隙锁。(这不包含查询条件中只包含复合唯一索引中某些列的场景, 在那种场景下是会产生间隙锁的。) 举例来说, 如果id
列有唯一索引, 下列语句会只对id列值为100的数据行使用索引记录锁, 无论其它会话是否会插入前面的间隙中插入某些行数据:
SELECT * FROM child WHERE id = 100;
如果id
列没有索引或者不是唯一索引, 这个语句就会锁住前面间隙。
这里同样值得注意的是不同事务可以在同一间隙中持有冲突的锁。 例如事务B持有排它间隙锁的时候事务A也可以持有同一间隙的一个共享间隙锁(gap S-lock)。 允许冲突的间隙锁存在的原因在于如果从索引中清除一条记录, 不同事务持有的记录上的间隙锁必然会被合并。
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)
一个临键锁是在索引记录上的记录锁与在这条索引记录前间隙上的间隙锁的组合。
InnoDB实现行级锁的方式是查询或扫描一个表的索引时在遇到的索引记录上设置共享或排它锁, 因此行级锁实际上是索引记录锁。 一个索引记录上的临键锁也会影响这条索引记录前的"间隙", 也就是说, 一个临键锁是一个索引记录所加上这条索引记录前的间隙锁。 如果一个会话持有索引中记录R的一个共享锁或者排它锁, 其它会话就不能在记录R前的间隙中插入一个新的索引记录。
假设一个索引包含值10、11、13和20, 该索引相关的临键锁涵盖以下区间(圆括号和中括号分别代表不包含和包含区间端点):
(负无穷, 10]
(10, 11]
(11, 13]
(13, 20]
(20, 正无穷)
对于最后一个区间, 临键锁会锁住索引最大值以上的间隙和值高于索引中会有的任意实际值的"上限"伪记录, 上限并不是一个真实索引记录, 所以实际上这个临键锁只是锁住在最大索引值后面的间隙。
InnoDB默认的是REPEATABLE READ事务隔离级别, 在这前提下, InnoDB在查询和索引扫描时使用临键锁, 从而防止幻读(详见14.7.4 幻读)。
"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 Intentions 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_INCREMENT列的表中插入数据的事务使用。 在最简单情况下, 如果某个事务正在向表中插入数据, 其它任何事务在执行各自插入操作前都必须等待, 这样第一个事务插入的数据行才会收到连续的主键值。
"innodb_autoinc_lock_mode"变量指定自增主键锁的算法, 可以用来选择如何权衡可预测的自增序列值和插入操作的最大并发性。
更多详细内容, 可以去看14.6.1.6 InnoDB中的"自增主键"。
Predicate Locks for Spatial Indexes
InnoDB supports SPATIAL indexing of columns containing spatial data (see Section 11.4.8, “Optimizing Spatial Analysis”).
To handle locking for operations involving SPATIAL indexes, next-key locking does not work well to support REPEATABLE READ or SERIALIZABLE transaction isolation levels. There is no absolute ordering concept in multidimensional data, so it is not clear which is the “next” key.
To enable support of isolation levels for tables with SPATIAL indexes, InnoDB uses predicate locks. A SPATIAL index contains minimum bounding rectangle (MBR) values, so InnoDB enforces consistent read on the index by setting a predicate lock on the MBR value used for a query. Other transactions cannot insert or modify a row that would match the query condition.
最后补充
- 原文档链接:MySQL 5.7 Reference Manual 14.7.1 InnoDB Locking
- 第八种空间索引相关的锁没有接触也没有了解过, 暂且放着。
- 后面会抽空补一下自行建表建索引执行SQL时各种锁的相关示例记录。
- 间隙锁和临键锁的更多细节有空也要再补些扩展内容。