InnoDB中的锁 - MySQL 8.0官方文档笔记(一)

d1zzyboy 2020-11-08 00:44:11
Mysql InnoDB 笔记 文档 官方


背景

最近看MySQL官方文档比较多,在此开坑翻译部分篇章,并附上一些旁注,用于展示实操结果,或者表达个人理解。

文档版本:8.0
来源:innodb-locking

此类形式为旁注。
本篇主要介绍InnoDB中的各类锁,而锁触发条件和应用场景不全在此篇中提及,后续会单独成篇进行讲解。

共享锁 & 独占锁

InnoDB 实现了两种类型的标准行锁:共享(S)锁和独占(X)锁。(下文简称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 上的锁。

这里讲到的S/X锁更倾向于在描述锁的模型:即锁的获取方式、资源控制能力和锁之间的交互。

接下来所讲到的各类锁是基于S/X模型来实现的,不同在于粒度、强弱等。

意向锁

InnoDB支持多粒度锁:即行锁与表锁共存。例如语句LOCK TABLES ... WRITE 获取表的X锁。InnoDB使用 意向锁 实现在多个粒度
上加锁。意向锁是表锁,用于指明一个事务稍后要获取哪种类型的行锁(S or X)。意向锁有两种类型:

  • 共享意向锁(IS):指明事务将要获取行的共享锁
  • 独占意向锁(IX):指明事务将要获取行的独占锁

例如,SELECT ... FOR SHARE获取了 IS 锁,SELECT ... FOR UPDATE 获取了 IX 锁。

注意5.6\5.7版本获取IS锁的语句有所不同:SELECT ... LOCK IN SHARE MODE

意向锁的使用原则:
一个事务若要获取行的 S 锁,必须先获取该表的 IS 锁或更强级别的锁。
一个事务若要获取行的 X 锁,必须先获取该表的 IX 锁。

表锁之间的兼容性总结如下:


X IX S IS
X 冲突 冲突 冲突 冲突
IX 冲突 兼容 冲突 兼容
S 冲突 冲突 兼容 兼容
IS 冲突 兼容 兼容 兼容

事务请求的锁必须和目前已产生的锁兼容,否则无法获取,直到冲突锁释放。而等待释放的过程如果InnoDB检测到存在死锁,则会抛出错误。

意向锁不会阻塞其他锁请求,除了表锁(如 LOCK TABLES ... WRITE)。意向锁是为了表明事务正在尝试获取,或将要获取行锁。

如果要查看当前数据库中的意向锁,执行 SHOW ENGINE INNODB STATUS , InnoDB 的监视器会输出如下内容:

TABLE LOCK table `test`.`t` trx id 10080 lock mode IX

如果在输出日志中看不到锁的相关信息,需要开启如下参数:
SET GLOBAL innodb_status_output_locks=ON;
见 : innodb-enabling-monitors

记录锁

记录锁用于锁住一条索引值。例如语句 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 8000000 a; asc ;;
1 : len 6 ; hex 00000000274 f; asc 'O;;
2 : len 7 ; hex b60000019d0110; asc ;;

trx id 10078 lock_mode X locks rec but not gap 意味记录锁只锁住了单条记录而没有锁定任何间隙,这也是通常主键查询的结果,关于间隙的概念下文中会介绍到。

针对查询条件没有覆盖索引时的情况,进行实验:
1.在一个表中加入自增主键,插入若干记录
2.删除主键列的索引
3.以原主键列的值作为查询条件执行SELECT FOR UPDATE

监视器输出:RECORD LOCKS space id 3 page no 6 n bits 320 index GEN_CLUST_INDEX of table `test`.`t` trx id 2131 lock_mode X
也就是使用了隐式生成的聚簇索引。
在这种情况下即使查询条件中的列在值上是唯一的,也会锁定全表记录(因为走了全表扫描)。
此时开启另一个事务,对另一条记录执行主键加锁查询(SELECT FOR UPDATE),根据S/X锁标准将被阻塞,经验证确实如此。

间隙锁

间隙锁用于锁定索引记录之间的间隙,或者一组索引值两端的间隙。比如语句 SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;
可以防止其他事务在t.c1列上插入 15 (因为在10-20之间),无论 15 是不是列上已有的值,因为在 BETWEEN 所指定的区间都被锁住了。

所谓的间隙可以覆盖一个值,多个值,甚至是 0 个。

关于间隙的准确含义此处引用官方术语集:
间隙 指能在InnoDB索引数据结构中能被插入的位置。例如用SELECT ... FOR UPDATE 锁住一批行时,InnoDB将锁住条件命中的索引上的值以及它们之间的间隙。比如加锁读所有大于10的值时,间隙锁会防止其他事务插入大于10的值。

间隙锁一定程度上体现了MySQL在性能与并发之间的权衡,在某些特定的事务隔离级别中使用到了间隙锁。

对于使用唯一索引查找的语句,不会用到间隙锁(除非搜索条件中只包含一个多列唯一索引的部分列)例如下列语句中,如果列 id 有唯一索引,则只会用到一个 id=100 的记录锁,并且不会妨碍其它会话在之前的间隙进行插入。

1 SELECT * FROM child WHERE id = 100 ;

但如果id没有索引或者有一个非唯一索引,语句就会锁住之前的间隙。

不同的事务可以在同一段间隙上持有相互冲突的锁。例如,事务A持有一段间隙的共享间隙锁(gap S-lock),同时事务B可以在同一段间隙上持有独占间隙锁(gap X-lock)。因为如果一条索引记录被删除,不同事务针对该记录持有的间隙锁必须被合并。

在InnoDB中,间隙锁的互斥特性被相当程度地抑制了,意思是间隙锁的唯一作用是防止事务在间隙中进行插入操作。间隙锁可以共存。不同事务可以同时持有同一段间隙的间隙锁。共享间隙锁和独占间隙锁没有区别。它们之间不会冲突,且作用相同。

间隙锁可以被显式禁用。通过改变事务隔离级别为 READ COMMITTED 或者开启系统变量 innodb_locks_unsafe_for_binlog(目前已弃用)
来禁用。在这些情况下,间隙锁不再用于搜索和索引扫描,而只用于外键约束检查和重复键检查。

上述的两种设置还有一些“副作用”。在MySQL计算出where条件后,不匹配的行的记录锁会被释放。对于UPDATE语句,InnoDB会执行一个“半一致性”读,从而向MySQL返回最新提交的版本号,用于挑选出匹配WHERE条件的行。

针对最后一段提到关于 READ COMMITTED 会释放不匹配的记录锁进行实验。

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
start TRANSACTION;
-- id未加索引
SELECT * from test.t where id =1 for update;

监视器输出:

RECORD LOCKS space id 3 page no 6 n bits 320 index >GEN_CLUST_INDEX of table `test`.`t` trx id 2178 lock_mode X locks rec but not gap

可以看到虽然走聚簇索引,但事务最终只占有符合筛选的记录锁。
隔离级别改回READ COMMITTED,执行相同语句。
监视器输出:

3 lock struct(s), heap size 1136, 372 row lock(s)
RECORD LOCKS space id 3 page no 6 n bits 320 index GEN_CLUST_INDEX of table `test`.`t` trx id 2179 lock_mode X

占有全表记录锁。

邻键锁

邻键锁是记录锁以及在索引记录之前间隙上的间隙锁的组合。

InnoDB在搜索或扫描索引时,对遇到的每一条索引记录设置共享/独占锁,以此实现行锁。因此,所谓的行锁实际就是记录锁。而邻键锁不仅仅锁住一条索引记录,还会影响记录之前的“间隙”。也就是说邻键锁可以表示为一个记录锁加上记录之前间隙的间隙锁。如果某个会话持有记录R上索引的共享/独占记录锁,对于其它会话,如果插入的值小于记录R上索引值(按索引排序规则),则不能直接插入,必须等待锁释放。

设想一个索引包含值 10 , 11 , 13 , 20 。那么所有可能的邻键锁区间如下,圆括号代表不包含,方括号代表包含:

 (负无穷, 10 ]
( 10 , 11 ]
( 11 , 13 ]
( 13 , 20 ]
( 20 , 正无穷)

对于最后一个区间,邻键锁锁定一段大于最大索引值的间隙,并使用一个虚拟的纪录表示上界。这个上界并不是真实的索引值,所以实际上这个邻键锁没有携带记录锁,只有大于当前索引最大值的间隙锁。

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 73757072656 d756d; asc supremum;;
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3 ; compact format; info bits 0
0 : len 4 ; hex 8000000 a; asc ;;
1 : len 6 ; hex 00000000274 f; asc 'O;;
2 : len 7 ; hex b60000019d0110; asc ;;

邻键锁主要用于解决幻行问题:事务因其它事务的插入操作导致两次读取的结果集不一致。邻键锁解决了这一问题,可帮助应用实现插入值唯一(加锁读->获取邻键锁->只允许本会话插入)。

插入意向锁

插入意向锁是一种特殊的间隙锁,在插入操作中执行行插入之前获得。用于标志插入的意向,从而使多个事务在同一段间隙执行插入时,如果对方不在同一个索引值位置上插入,则无需互相等待。例如,当前索引值有 4 和 7 。两个事务分别准备插入 5 和 6 ,在获取被插入行的独占锁之前,它们会各自获取 4 至 7 之间间隙的插入意向锁,且不会互相阻塞,因为插入值没有冲突。

下面通过一个例子来演示事务在获取记录的独占锁之前,获取插入意向锁的过程。案例中涉及两个客户端,分别是A和B。

客户端A创建一张表,包含两条索引值( 90 和 102),然后开启一个事务,获取ID大于100的所有记录的独占锁。独占锁将包含一个 id<102 的间隙锁:

即邻键锁,区间 =(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 的监视器会输出如下内容:

1 RECORD LOCKS space id 31 page no 3 n bits 72 index `PRIMARY` of table `test`.`child`
2 trx id 8731 lock_mode X locks gap before rec insert intention waiting
3 Record lock, heap no 3 PHYSICAL RECORD: n_fields 3 ; compact format; info bits 0
4 0 : len 4 ; hex 80000066 ; asc f;;
5 1 : len 6 ; hex 000000002215 ; asc " ;;
6 2 : len 7 ; hex 9000000172011 c; asc r ;;...

自增锁

自增锁是一种特殊的表锁,事务在带有自增列的表中执行插入会获取自增锁。在最简单的情形中,如果某个事务在向表中插入数据,其它事务必须等待其插入完毕才能执行自己的插入,以此来保证主键值是连续的。

配置项 innodb_autoinc_lock_mode 用于控制自增锁使用的算法,以帮助你在自增序列的可预测性和插入的并发能力之间权衡。

针对空间索引的断言锁

InnoDB支持对空间行建立索引。
在处理涉及到空间索引的操作时,邻键锁在 REPEATABLE READSERIALIZABLE 两个隔离级别上不能很好的工作。因为对于多维数据没有绝对的排序规则,所以并不能明确谁才是“邻键”。

版权声明
本文为[d1zzyboy]所创,转载请带上原文链接,感谢
https://www.cnblogs.com/notayeser/p/13943097.html

  1. 【计算机网络 12(1),尚学堂马士兵Java视频教程
  2. 【程序猿历程,史上最全的Java面试题集锦在这里
  3. 【程序猿历程(1),Javaweb视频教程百度云
  4. Notes on MySQL 45 lectures (1-7)
  5. [computer network 12 (1), Shang Xuetang Ma soldier java video tutorial
  6. The most complete collection of Java interview questions in history is here
  7. [process of program ape (1), JavaWeb video tutorial, baidu cloud
  8. Notes on MySQL 45 lectures (1-7)
  9. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  10. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  11. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  12. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  13. 【递归,Java传智播客笔记
  14. [recursion, Java intelligence podcast notes
  15. [adhere to painting for 386 days] the beginning of spring of 24 solar terms
  16. K8S系列第八篇(Service、EndPoints以及高可用kubeadm部署)
  17. K8s Series Part 8 (service, endpoints and high availability kubeadm deployment)
  18. 【重识 HTML (3),350道Java面试真题分享
  19. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  20. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  21. [re recognize HTML (3) and share 350 real Java interview questions
  22. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  23. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  24. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  25. RPC 1: how to develop RPC framework from scratch
  26. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  27. RPC 1: how to develop RPC framework from scratch
  28. 一次性捋清楚吧,对乱糟糟的,Spring事务扩展机制
  29. 一文彻底弄懂如何选择抽象类还是接口,连续四年百度Java岗必问面试题
  30. Redis常用命令
  31. 一双拖鞋引发的血案,狂神说Java系列笔记
  32. 一、mysql基础安装
  33. 一位程序员的独白:尽管我一生坎坷,Java框架面试基础
  34. Clear it all at once. For the messy, spring transaction extension mechanism
  35. A thorough understanding of how to choose abstract classes or interfaces, baidu Java post must ask interview questions for four consecutive years
  36. Redis common commands
  37. A pair of slippers triggered the murder, crazy God said java series notes
  38. 1、 MySQL basic installation
  39. Monologue of a programmer: despite my ups and downs in my life, Java framework is the foundation of interview
  40. 【大厂面试】三面三问Spring循环依赖,请一定要把这篇看完(建议收藏)
  41. 一线互联网企业中,springboot入门项目
  42. 一篇文带你入门SSM框架Spring开发,帮你快速拿Offer
  43. 【面试资料】Java全集、微服务、大数据、数据结构与算法、机器学习知识最全总结,283页pdf
  44. 【leetcode刷题】24.数组中重复的数字——Java版
  45. 【leetcode刷题】23.对称二叉树——Java版
  46. 【leetcode刷题】22.二叉树的中序遍历——Java版
  47. 【leetcode刷题】21.三数之和——Java版
  48. 【leetcode刷题】20.最长回文子串——Java版
  49. 【leetcode刷题】19.回文链表——Java版
  50. 【leetcode刷题】18.反转链表——Java版
  51. 【leetcode刷题】17.相交链表——Java&python版
  52. 【leetcode刷题】16.环形链表——Java版
  53. 【leetcode刷题】15.汉明距离——Java版
  54. 【leetcode刷题】14.找到所有数组中消失的数字——Java版
  55. 【leetcode刷题】13.比特位计数——Java版
  56. oracle控制用户权限命令
  57. 三年Java开发,继阿里,鲁班二期Java架构师
  58. Oracle必须要启动的服务
  59. 万字长文!深入剖析HashMap,Java基础笔试题大全带答案
  60. 一问Kafka就心慌?我却凭着这份,图灵学院vip课程百度云