[译]MySQL中InnoDB的锁分类

概要

文章翻译自15.7.1 InnoDB Locking,其中所有锁特性都以mysql-8.0版本为准,其中包含的锁种类如下:

  • 共享锁和排它锁
  • 意向锁
  • 记录锁
  • 间隙锁
  • 后键锁
  • 插入意向锁
  • 自增锁
  • 空间索引的谓词锁

共享锁和排它锁(Shared and Exclusive Locks)

Innodb实现了标准的行级锁,包括共享锁和排它锁两种类型

  • 共享锁(s锁)允许拥有该锁的事务读取该行的记录
  • 排它锁(x锁)允许拥有该锁的事务更新或删除该行的记录

如果事务T1拥有行r上的共享锁,那么对于不同的事务T2的对行r上的请求,有以下处理方式:

  • 可以立即授予T2共享锁,最终T1和T2都拥有共享锁
  • 不能立即授予T2排它锁

如果事务T1拥有行r上的排它锁,则对于不同的事务T2,不能授予其行r上的任何锁,T2必须等待T1释放其对行r的锁定

意向锁(Intention Locks)

InnoDB支持多种粒度的锁定,来允许行锁和表锁同时存在,例如LOCK TABLE ... WRITE语句可以在指定的表上使用排它锁。为了实现多粒度锁定,InnoDB使用了意向锁

意向锁是表级锁,它表示一个事务稍后对表中的行需要使用的锁类型,意向锁有两种类型:

  • 意向共享锁(IS锁)表示一个事务倾向于对表中的部分行设置共享锁
  • 意向排它锁(IX锁)表示一个事务倾向于对表中的部分行设置排它锁

例如,SELECT ... FOR SHARE会设置IS锁,SELECT ... FOR UPDATE会设置IX锁

意向锁的协议如下:

  • 在事务可以获取表中某行的共享锁之前,必须先在表上获取IS锁,或表上更强的锁
  • 在事务可以获取表中某些行的排它锁之前,必须先在表上获取IX锁

表级锁的兼容性通过以下的矩阵进行了总结:

如果请求事务与当前存在的锁兼容,则授予锁。如果冲突则不会授予,事务会进行等待,直到冲突的锁被释放。永远不会在冲突情况下授予锁,因为会导致数据库的死锁

意向锁不会阻塞除全表请求外的任何内容(如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会创建一个隐藏的聚集索引,并使用此索引来进行记录锁定

记录锁锁定的事务数据在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为100的锁使用索引记录锁,而不关心其他的会话是否在前一个间隙中插入行:

SELECT * FROM child WHERE id = 100;
复制代码

如果id列没有索引,或使用的是非唯一索引,则该语句会锁定前一个间隙

值得注意的是,互相冲突的锁可以通过不同的事务在间隙上保持,例如,事务A在间隙上保持共享的间隙锁(间隙S锁),事务B可以在同一间隙上保持独占的间隙锁(间隙X锁)。允许互相冲突的间隙锁的原因是,如果从索引中清除记录,则必须合并由不同事务保留在记录上的间隙锁

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

可以通过明确的声明来禁用间隙锁,比如将事务隔离级别设置为READ COMMITTED。在这种情况下,对于搜索和索引扫描会禁用间隙锁,仅会作用于外键约束和重复键的检查

使用READ COMMITTED隔离级别还会有其他影响,MySQL评估WHERE条件后,会释放不匹配行的记录锁。对于UPDATE语句,InnoDB执行“半一致”读取,使之能将最新的版本提交给MySQL,MySQL就可以确定该行是否与UPDATEWHERE条件匹配

后键锁(Next-Key Locks)

后键锁,是索引记录上的记录锁和索引记录之前的间隙锁的组合

InnoDB使用这样一种方式来进行行级锁定:当搜索或扫描表索引时,会在遇到的索引记录上设置共享锁或排它锁,因此,行级锁实际上是索引记录锁

索引记录上的后键锁也会影响该索引记录之前的“间隙”,也就是说后键锁是索引记录锁加上该索引记录之前的间隙上的间隙锁。如果一个会话在索引中的记录R上有共享锁或排它锁,则另一个会话不能在索引顺序中的R之前的间隙中插入新的索引记录

假设索引中包含值10、11、13,和20,此索引可能的后键锁会覆盖以下键值,其中圆括号表示排除的间隔端点,方括号表示包含的端点(即开集和闭集的概念):

(negative infinity, 10]
(10, 11]
(11, 13]
(13, 20]
(20, positive infinity)
复制代码

对于最后一个间隔,后键锁将间隙锁定在索引中最大值之上,且伪记录“supermum”的值高于索引中任何的实际值,supermun不是真正的索引记录,因此,实际上这个后键锁(指最后一个间隙的后键锁)仅锁定最大索引值之后的间隙

默认情况下,InnoDB在REPEATABLE READ隔离级别下运行,在这种情况下,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 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创建了一个包含两个索引记录(99和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配置选项用来控制用于自增锁定的算法,它允许您在可预测的自增值序列和插入操作的最大并发量之间权衡

空间索引的谓词锁(Predicate Locks for Spatial Indexes)

InnoDB支持包含空间性列的SPATIAL索引(参考11.5.9 Optimizing Spatial Analysis

要处理涉及SPATIAL索引的操作的锁定,后键锁不能很好地支持REPEATABLE READSERIALIZABLE事务隔离级别,多维数据中没有绝对有序的概念,因此不能准确地判断哪一个是“后键”

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

转载于:https://juejin.im/post/5cef617e518825095b331caf

猜你喜欢

转载自blog.csdn.net/weixin_34306676/article/details/91482482