mysql gen lock_InnoDB中的锁 - MySQL 8.0官方文档笔记(一)

背景

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

文档版本:8.0

来源:innodb-lockingmysql

此类形式为旁注。

本篇主要介绍InnoDB中的各种锁,而锁触发条件和应用场景不全在此篇中说起,后续会单独成篇进行讲解。算法

共享锁 & 独占锁

InnoDB 实现了两种类型的标准行锁:共享(S)锁和独占(X)锁。(下文简称S锁和X锁)sql

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)。意向锁有两种类型:spa

共享意向锁(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 READ 和 SERIALIZABLE 两个隔离级别上不能很好的工做。由于对于多维数据没有绝对的排序规则,因此并不能明确谁才是“邻键”。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值