Record锁/Gap锁/Next-key锁/插入意向锁

2024-08-21 14:08

本文主要是介绍Record锁/Gap锁/Next-key锁/插入意向锁,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章总共分为五个部分:

  • InnoDB的锁机制浅析(一)—基本概念/兼容矩阵
  • InnoDB的锁机制浅析(二)—探索InnoDB中的锁(Record锁/Gap锁/Next-key锁/插入意向锁)
  • InnoDB的锁机制浅析(三)—幻读
  • InnoDB的锁机制浅析(四)—不同SQL的加锁状况
  • InnoDB的锁机制浅析(五)—死锁场景(Insert死锁)

大而全版(五合一):InnoDB的锁机制浅析(All in One)

前言

InnoDB常见的锁有Record锁、gap锁、next-key锁、插入意向锁、自增锁等。
下面会对每一种锁给出一个查看锁的示例。

常见的锁有Record锁、gap锁、next-key锁、插入意向锁、自增锁等。
下面会对每一种锁给出一个查看锁的示例。

1 准备工作

1.1 测试用表结构

示例的基础是一个只有两列的数据库表。

mysql> CREATE TABLE test (
id int(11) NOT NULL,
code int(11) NOT NULL, 
PRIMARY KEY(id), 
KEY (code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; mysql> INSERT INTO test(id,code) values(1,1),(10,10);

数据表test只有两列,id是主键索引,code是普通的索引(注意,一定不要是唯一索引),并初始化了两条记录,分别是(1,1),(10,10)。
这样,我们验证唯一键索引就可以使用id列,验证普通索引(非唯一键二级索引)时就使用code列。

1.2 查看锁状态的方式

要看到锁的情况,必须手动开启多个事务,其中一些锁的状态的查看则必须使锁处于waiting状态,这样才能在mysql的引擎状态日志中看到。

命令:

mysql> show engine innodb status;

这条命令能显示最近几个事务的状态、查询和写入情况等信息。当出现死锁时,命令能给出最近的死锁明细。

2 记录锁 Record Locks

Record锁

Record Lock是对索引记录的锁定。记录锁有两种模式,S模式和X模式。
例如SELECT id FROM test WHERE id = 10 FOR UPDATE;表示防止任何其他事务插入、更新或者删除id =10的行。

记录锁始终只锁定索引。即使表没有建立索引,InnoDB也会创建一个隐藏的聚簇索引(隐藏的递增主键索引),并使用此索引进行记录锁定。

查看记录锁

开启第一个事务,不提交,测试完之后回滚。

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)mysql> update test set id=2 where id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

事务加锁情况

mysql> show engine innodb status\G;
... 
------------
TRANSACTIONS
------------
---TRANSACTION 366811, ACTIVE 690 sec
2 lock struct(s), heap size 1136, 1 row lock(s), undo log entries 2
MySQL thread id 785, OS thread handle 123145432457216, query id 729076 localhost 127.0.0.1 root
...

可以看到有一行被加了锁。由之前对锁的描述可以推测出,update语句给id=1这一行上加了一个X锁

注意:X锁广义上是一种抽象意义的排它锁,即锁一般分为X模式S模式,狭义上指row或者index上的锁,而Record锁是索引上的锁。
为了不修改数据,可以用select ... for update语句,加锁行为和updatedelete是一样的,insert加锁机制较为复杂,后面的章节会提到。

第一个事务保持原状,不要提交或者回滚,现在开启第二个事务。

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)mysql> update test set id=3 where id=1;

执行update时,sql语句的执行被阻塞了。查看下事务状态:

mysql> show engine innodb status\G;
...
------- TRX HAS BEEN WAITING 4 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 62 page no 3 n bits 72 index PRIMARY of table `test`.`test` trx id 366820 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 320: len 8; hex 0000000000000001; asc         ;;1: len 6; hex 0000000598e3; asc       ;;2: len 7; hex 7e000001a80896; asc ~      ;;------------------
...

喜闻乐见,我们看到了这个锁的状态。状态标题是'事务正在等待获取锁',描述中的lock_mode X locks rec but not gap就是本章节中的record记录锁,直译一下'X锁模式锁住了记录'。后面还有一句but not gap意思是只对record本身加锁,并不对间隙加锁,间隙锁的叙述见下一个章节。

3 间隙锁 Gap Locks

间隙锁

间隙锁作用在索引记录之间的间隔,又或者作用在第一个索引之前,最后一个索引之后的间隙。不包括索引本身。
例如,SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;这条语句阻止其他事务插入10和20之间的数字,无论这个数字是否存在。

间隙可以跨越0个,单个或多个索引值。

间隙锁是性能和并发权衡的产物,只存在于部分事务隔离级别。

select * from table where id=1;
唯一索引可以锁定一行,所以不需要间隙锁锁定。
如果列没有索引或者具有非唯一索引,该语句会锁定当前索引前的间隙。

在同一个间隙上,不同的事务可以持有上述兼容/冲突表中冲突的两个锁。例如,事务T1现在持有一个间隙S锁,T2可以同时在同一个间隙上持有间隙X锁。
允许冲突的锁在间隙上锁定的原因是,如果从索引中清除一条记录,则由不同事务在这条索引记录上的加间隙锁的动作必须被合并。

InnoDB中的间隙锁的唯一目的是防止其他事务插入间隙。
间隙锁是可以共存的,一个事务占用的间隙锁不会阻止另一个事务获取同一个间隙上的间隙锁。

如果事务隔离级别改为RC,则间隙锁会被禁用。

查看间隙锁

按照官方文档,where子句查询条件是唯一键且指定了值时,只有record锁,没有gap锁。
如果where语句指定了范围,gap锁是存在的。
这里只测试验证一下当指定非唯一键索引的时候,gap锁的位置,按照文档的说法,会锁定当前索引及索引之前的间隙。(指定了非唯一键索引,例如code=10,间隙锁仍然存在)

开启第一个事务,锁定一条非唯一的普通索引记录

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)mysql> select * from test where code = 10 for update;
+----+------+
| id | code |
+----+------+
| 10 |   10 |
+----+------+
1 row in set (0.00 sec)

由于预存了两条数据,row(1,1)和row(10,10),此时这个间隙应该是1<gap<10。我们先插入row(2,2)来验证下gap锁的存在,再插入row(0,0)来验证gap的边界。

按照间隙锁的官方文档定义,select * from test where code = 10 for update;会锁定code=10这个索引,并且会锁定code<10的间隙。

开启第二个事务,在code=10之前的间隙中插入一条数据,看下这条数据是否能够插入。

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)mysql> insert into test values(2,2);

插入的时候,执行被阻塞,查看引擎状态:

mysql> show engine innodb status\G;
...
---TRANSACTION 366864, ACTIVE 5 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s), undo log entries 1
MySQL thread id 793, OS thread handle 123145434963968, query id 730065 localhost 127.0.0.1 root update
insert into test values(2,2)
------- TRX HAS BEEN WAITING 5 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 63 page no 4 n bits 72 index code of table `test`.`test` trx id 366864 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 00: len 8; hex 800000000000000a; asc         ;;1: len 8; hex 000000000000000a; asc         ;;------------------
...

插入语句被阻塞了,lock_mode X locks gap before rec,由于第一个事务锁住了1到10之间的gap,需要等待获取锁之后才能插入。

如果再开启一个事务,插入(0,0)

mysql> start transaction;
mysql> insert into test values(0,0);
Query OK, 1 row affected (0.00 sec)

可以看到:指定的非唯一建索引的gap锁的边界是当前索引到上一个索引之间的gap

最后给出锁定区间的示例,首先插入一条记录(5,5)

mysql> insert into test values(5,5);
Query OK, 1 row affected (0.00 sec)

开启第一个事务:

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)mysql> select * from test where code between 1 and 10 for update;
+----+------+
| id | code |
+----+------+
|  1 |    1 |
|  5 |    5 |
| 10 |   10 |
+----+------+
3 rows in set (0.00 sec)

第二个事务,试图去更新code=5的行:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)mysql> update test set code=4 where code=5;

执行到这里,如果第一个事务不提交或者回滚的话,第二个事务一直等待直至mysql中设定的超时时间。

4 Next-key Locks

Next-key锁

Next-key锁实际上是Record锁和gap锁的组合。Next-key锁是在下一个索引记录本身和索引之前的gap加上S锁或是X锁(如果是读就加上S锁,如果是写就加X锁)。
默认情况下,InnoDB的事务隔离级别为RR,系统参数innodb_locks_unsafe_for_binlog的值为false。InnoDB使用next-key锁对索引进行扫描和搜索,这样就读取不到幻象行,避免了幻读的发生。

幻读是指在同一事务下,连续执行两次同样的SQL语句,第二次的SQL语句可能会返回之前不存在的行。

当查询的索引是唯一索引时,Next-key lock会进行优化,降级为Record Lock,此时Next-key lock仅仅作用在索引本身,而不会作用于gap和下一个索引上。

查看Next-key锁

Next-key锁的作用范围

如上述例子,数据表test初始化了row(1,1),row(10,10),然后插入了row(5,5)。数据表如下:

mysql> select * from test;
+----+------+
| id | code |
+----+------+
|  1 |    1 |
|  5 |    5 |
| 10 |   10 |
+----+------+
3 rows in set (0.00 sec)

由于id是主键、唯一索引,mysql会做优化,因此使用code这个非唯一键的二级索引来举例说明。

对于code,可能的next-key锁的范围是:

(-∞,1]
(1,5]
(5,10]
(10,+∞)

开启第一个事务,在code=5的索引上请求更新:

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)mysql> select * from test where code=5 for update;
+----+------+
| id | code |
+----+------+
|  5 |    5 |
+----+------+
1 row in set (8.81 sec)

之前在gap锁的章节中介绍了,code=5 for update会在code=5的索引上加一个record锁,还会在1<gap<5的间隙上加gap锁。现在不再验证,直接插入一条(8,8):

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into test values(8);

insert处于等待执行的状态,这就是next-key锁生效而导致的结果。第一个事务,锁定了区间(1,5],由于RR的隔离级别下next-key锁处于开启生效状态,又锁定了(5,10]区间。所以插入SQL语句的执行被阻塞。

解释:在这种情况下,被锁定的区域是code=5前一个索引到它的间隙,以及next-key的区域。code=5 for update对索引的锁定用区间表示,gap锁锁定了(1,5),record锁锁定了{5}索引记录,next-key锁锁住了(5,10],也就是说整个(1,10]的区间被锁定了。由于是for update,所以这里的锁都是X锁,因此阻止了其他事务中带有冲突锁定的操作执行。

如果我们在第一个事务中,执行了code>8 for update,在扫描过程中,找到了code=10,此时就会锁住10之前的间隙(5到10之间的gap),10本身(record),和10之后的间隙(next-key)。此时另一个事务插入(6,6),(9,9)和(11,11)都是不被允许的,只有在前一个索引5及5之前的索引和间隙才能执行插入(更新和删除也会被阻塞)。

5 插入意向锁 Insert Intention Locks

插入意向锁在行插入之前由INSERT设置一种间隙锁,是意向排它锁的一种。
在多事务同时写入不同数据至同一索引间隙的时,不会发生锁等待,事务之间互相不影响其他事务的完成,这和间隙锁的定义是一致的。

假设一个记录索引包含4和7,其他不同的事务分别插入5和6,此时只要行不冲突,插入意向锁不会互相等待,可以直接获取。参照锁兼容/冲突矩阵。
插入意向锁的例子不再列举,可以查看gap锁的第一个例子。

6 自增锁

自增锁(AUTO-INC Locks)是事务插入时自增列上特殊的表级别的锁。最简单的一种情况:如果一个事务正在向表中插入值,则任何其他事务必须等待,以便第一个事务插入的行接收连续的主键值。

我们一般把主键设置为AUTO_INCREMENT的列,默认情况下这个字段的值为0,InnoDB会在AUTO_INCREMENT修饰下的数据列所关联的索引末尾设置独占锁。在访问自增计数器时,InnoDB使用自增锁,但是锁定仅仅持续到当前SQL语句的末尾,而不是整个事务的结束,毕竟自增锁是表级别的锁,如果长期锁定会大大降低数据库的性能。由于是表锁,在使用期间,其他会话无法插入表中。

 

https://www.cnblogs.com/AaronCui/p/10508766.html

这篇关于Record锁/Gap锁/Next-key锁/插入意向锁的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1093374

相关文章

Java枚举类实现Key-Value映射的多种实现方式

《Java枚举类实现Key-Value映射的多种实现方式》在Java开发中,枚举(Enum)是一种特殊的类,本文将详细介绍Java枚举类实现key-value映射的多种方式,有需要的小伙伴可以根据需要... 目录前言一、基础实现方式1.1 为枚举添加属性和构造方法二、http://www.cppcns.co

MySQL INSERT语句实现当记录不存在时插入的几种方法

《MySQLINSERT语句实现当记录不存在时插入的几种方法》MySQL的INSERT语句是用于向数据库表中插入新记录的关键命令,下面:本文主要介绍MySQLINSERT语句实现当记录不存在时... 目录使用 INSERT IGNORE使用 ON DUPLICATE KEY UPDATE使用 REPLACE

Jmeter如何向数据库批量插入数据

《Jmeter如何向数据库批量插入数据》:本文主要介绍Jmeter如何向数据库批量插入数据方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Jmeter向数据库批量插入数据Jmeter向mysql数据库中插入数据的入门操作接下来做一下各个元件的配置总结Jmete

MySQL中Next-Key Lock底层原理实现

《MySQL中Next-KeyLock底层原理实现》Next-KeyLock是MySQLInnoDB存储引擎中的一种锁机制,结合记录锁和间隙锁,用于高效并发控制并避免幻读,本文主要介绍了MySQL中... 目录一、Next-Key Lock 的定义与作用二、底层原理三、源代码解析四、总结Next-Key L

使用Python在Excel中插入、修改、提取和删除超链接

《使用Python在Excel中插入、修改、提取和删除超链接》超链接是Excel中的常用功能,通过点击超链接可以快速跳转到外部网站、本地文件或工作表中的特定单元格,有效提升数据访问的效率和用户体验,这... 目录引言使用工具python在Excel中插入超链接Python修改Excel中的超链接Python

深入理解Redis大key的危害及解决方案

《深入理解Redis大key的危害及解决方案》本文主要介绍了深入理解Redis大key的危害及解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着... 目录一、背景二、什么是大key三、大key评价标准四、大key 产生的原因与场景五、大key影响与危

python 字典d[k]中key不存在的解决方案

《python字典d[k]中key不存在的解决方案》本文主要介绍了在Python中处理字典键不存在时获取默认值的两种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录defaultdict:处理找不到的键的一个选择特殊方法__missing__有时候为了方便起见,

你的华为手机升级了吗? 鸿蒙NEXT多连推5.0.123版本变化颇多

《你的华为手机升级了吗?鸿蒙NEXT多连推5.0.123版本变化颇多》现在的手机系统更新可不仅仅是修修补补那么简单了,华为手机的鸿蒙系统最近可是动作频频,给用户们带来了不少惊喜... 为了让用户的使用体验变得很好,华为手机不仅发布了一系列给力的新机,还在操作系统方面进行了疯狂的发力。尤其是近期,不仅鸿蒙O

顺序表之创建,判满,插入,输出

文章目录 🍊自我介绍🍊创建一个空的顺序表,为结构体在堆区分配空间🍊插入数据🍊输出数据🍊判断顺序表是否满了,满了返回值1,否则返回0🍊main函数 你的点赞评论就是对博主最大的鼓励 当然喜欢的小伙伴可以:点赞+关注+评论+收藏(一键四连)哦~ 🍊自我介绍   Hello,大家好,我是小珑也要变强(也是小珑),我是易编程·终身成长社群的一名“创始团队·嘉宾”

git ssh key相关

step1、进入.ssh文件夹   (windows下 下载git客户端)   cd ~/.ssh(windows mkdir ~/.ssh) step2、配置name和email git config --global user.name "你的名称"git config --global user.email "你的邮箱" step3、生成key ssh-keygen