MySQL 中 InnoDB 的锁

共享锁和排他锁

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 上的锁。

意向锁

InnoDB 支持多粒度锁定,允许行锁和表锁并存。例如,一个像 LOCK TABLES ... WRITE 这样的语句会对指定的表取得排他锁(一个 X 锁)。为了使多粒度级别上的锁定实用,InnoDB 使用意向锁。意向锁是表级别的锁,表明事务稍后需要对表中的行设置哪种类型的锁(共享或排他)。意向锁有两种类型:

  • 意向共享锁 (IS) 表示事务打算在表中的各个行上设置共享锁。
  • 意向排他锁 (IX) 表示事务打算在表中的各个行上设置排他锁。

例如,SELECT ... FOR SHARE 设置一个 IS 锁,而 SELECT ... FOR UPDATE 设置一个 IX 锁。

意向锁协议如下:

  • 在一个事务能够在表中的一行上获取共享锁之前,它必须首先获取表上的 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
记录锁

记录锁是对索引记录的锁。例如,SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE; 阻止其他事务插入、更新或删除 t.c1 值为 10 的行。

记录锁始终锁定索引记录,即使表定义时没有索引。对于这些情况,InnoDB 创建一个隐藏的聚集索引并使用此索引进行记录锁定。

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        ;;
间隙锁

间隙锁是对索引记录之间的间隙的锁定,或者是对第一个或最后一个索引记录之前的间隙的锁定。例如,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-lock),而事务 B 在同一间隙上持有排他间隙锁(间隙 X-lock)。允许冲突间隙锁的原因是,如果从索引中清除了一条记录,则不同事务持有的记录上的间隙锁必须合并。

InnoDB 中的间隙锁是“纯粹抑制性”的,这意味着它们的唯一目的是阻止其他事务向间隙中插入。间隙锁可以共存。一项事务获取的间隙锁不会阻止另一个事务在同一个间隙上获取间隙锁。共享和排他间隙锁之间没有区别。它们彼此不冲突,功能相同。

间隙锁可以明确地被禁用。这种情况发生在您将事务隔离级别更改为 READ COMMITTED 时。在这种情况下,间隙锁定对于搜索和索引扫描被禁用,仅用于外键约束检查和重复键检查。

使用 READ COMMITTED 隔离级别还有其他影响。对于不匹配行的记录锁在 MySQL 评估 WHERE 条件后被释放。对于 UPDATE 语句,InnoDB 进行“半一致性”读取,这样它返回最新的提交版本给 MySQL,以便 MySQL 可以确定该行是否与 UPDATE 的 WHERE 条件匹配。

Next-Key 锁

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 锁,从而防止“幻影行”。

SHOW ENGINE INNODB STATUS 和 InnoDB 监控输出中,next-key 锁的事务数据类似于以下内容:

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 的不同事务,在获取插入行的排他锁之前,分别用插入意向锁锁定 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 锁

AUTO-INC 锁是事务插入带有 AUTO_INCREMENT 列的表时采用的一种特殊表级锁。在最简单的情况下,如果一个事务正在表中插入值,则任何其他事务都必须等待在该表中进行自己的插入,以便由第一个事务插入的行获得连续的主键值。

innodb_autoinc_lock_mode 变量控制用于自增锁定的算法。它允许您在可预测的自增值序列和插入操作的最大并发性之间进行选择。

空间索引的谓词锁

InnoDB 支持对包含空间数据的列进行 SPATIAL 索引。

为了支持涉及 SPATIAL 索引的操作的锁定,next-key 锁定不适用于支持 REPEATABLE READ 或 SERIALIZABLE 事务隔离级别。在多维数据中没有绝对排序概念,所以不清楚哪个是“下一个”键。

为了支持带有 SPATIAL 索引的表的隔离级别,InnoDB 使用谓词锁。SPATIAL 索引包含最小边界矩形(MBR)值,因此 InnoDB 通过在查询中使用的 MBR 值上设置谓词锁来强制索引的一致性读取。其他事务无法插入或修改将符合查询条件的行。

你可能感兴趣的:(mysql,mysql,数据库)