Mysql 在共享锁模式和更新的情况下,间隙锁是如何工作的?

Mysql 在共享锁模式和更新的情况下,间隙锁是如何工作的?,mysql,Mysql,先决条件: mysql version: 5.7.31 isolation level: RR 表格构建说明如下所示: CREATE TABLE `lockt` ( `id` int(11) NOT NULL, `col1` int(11) DEFAULT NULL, `col2` int(11) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `col1_ind` (`col1`), KEY `col2_ind` (`col2

先决条件:

mysql version: 5.7.31
isolation level: RR
表格构建说明如下所示:

CREATE TABLE `lockt` (
  `id` int(11) NOT NULL,
  `col1` int(11) DEFAULT NULL,
  `col2` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `col1_ind` (`col1`),
  KEY `col2_ind` (`col2`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
有一些数据可供测试:

INSERT INTO `lockt` (`id`, `col1`, `col2`)
VALUES
    (1,1,1),
    (2,2,3),
    (5,5,5),
    (6,6,9),
    (10,10,25),
    (123,123,8),
    (1007,10077,144),
    (1008,1008,220),
    (1019,1019,200),
    (1020,1020,201),
    (1111,1111,32),
    (1234,1234,33);
在学习mysql中的gap lock的过程中,我遇到了一个让我难以理解的案例:

在事务1中:

set autocommit=0;
begin;
select * from lockt;
select * from lockt where col2=25 lock in share mode;
然后,我启动另一个事务:

set autocommit=0;
begin;
select * from lockt;
update lockt set col2=66666 where col2 > 33;

但我发现update语句被阻止了。在我看来,SQL“select*from lock where col2=25 lock In share mode”将应用共享锁,并在范围(9,25),(25,32)上添加间隙锁,但范围(33+∞] 超出了这些范围,为什么第二个事务仍然被阻止,这超出了我的预期。我很困惑为什么它会这样。我对间隙锁有什么特别的地方误解了吗?任何能帮我解决这个问题的人都将不胜感激。

添加一些关于锁的背景知识(并且使这个答案比预期的长一点),重要的是要理解,一般来说,MySQL只考虑在执行查询时锁定它所看到的行/空。 所有其他行/间隙都是不相关的:如果第二个查询既不查看相同的行/间隙,也不会对这些行/间隙进行更改(例如,通过添加行),则它无法更改第一个查询的结果,也无法更改第二个查询的结果,无论第一个查询执行什么操作

因此,要研究锁,了解MySQL查看哪些行是绝对重要的(如果锁确实存在冲突,则取决于查询和隔离级别,这是您尝试尝试使用的,但实际上不是答案的一部分,因为这不是造成混淆的原因)

那么MySQL是如何找到您的行的呢?MySQL显然可以使用
col2
上的索引。如果MySQL使用索引来查找行,它会同时锁定索引和行本身(从技术上讲,这意味着它会锁定主键中的一行)

这就是您所期望的:MySQL查找具有
col2=25
的行,锁定它(在索引和主键中),然后应该查找具有
col2>33
的行,并且查找使用索引的行不应该冲突

这是正确的。如果MySQL使用索引,就没有重叠。令人困惑的是,MySQL有一种不同的方式来执行查询:它可以遍历整个表并更新符合您条件的行

这实际上可以更快(这是优化器所关心的),因为使用索引查找每行要比遍历整个表慢。这只是一个数字游戏:在某些情况下,只读取所有行(以更高的每行速度)比使用索引查找正确的行(以更低的每行速度)要快

显然,对于
col2>33
,MySQL决定这样做。既然这样做了,它查看的行现在都是行。这将与
col2=25
上的锁冲突(该锁已使用col2`上的索引锁定在主键中)。这不是因为gaplock(您正在尝试调查),只是一个简单的普通锁定行

您可以使用较大的值重试查询,然后MySQL可能会决定使用索引。您可以通过运行
explain update lock…
检查MySQL使用的索引,并根据您的注释,检查临界值(以及您的特定数据)似乎是
col2>144
。为此,执行计划应显示is使用
col2
上的索引(列
key
中的值),而对于
col2>143
,则应使用主键

实际上,您可以使用如下索引提示强制MySQL使用您希望它使用的索引(按预期锁定)

update lockt force index (col2_ind) set col2=66666 where col2 > 33;
再次强调:MySQL不必锁定它查看的所有行和/或间隙,如果未使用(例如,如果未更新),它可以释放锁,并且并非所有锁都与所有锁冲突。有关这一点的详细信息将取决于查询和隔离级别,并将允许MySQL进行广泛的实际锁定行为

简单地回答你的问题:MySQL锁是基于索引的,所以如果你尝试使用锁,请确保你检查了索引。

同意@Solarflare

在会话1中:使用共享锁锁定col2数据行。 在会话2中:使用条件col2>33执行udpate语句并锁定查询。 然后我们可以使用“显示引擎innodb状态”来显示锁的详细信息。 例如:

TRANSACTIONS
------------
Trx id counter 8182
Purge done for trx's n:o < 8177 undo n:o < 0 state: running but idle
History list length 3
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 281479682839888, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 281479682837176, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 281479682838984, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 8181, ACTIVE 45 sec fetching rows
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 6 row lock(s)
MySQL thread id 25, OS thread handle 123145535516672, query id 3434 localhost root updating
update lockt set col2=111 where col2 >33
------- TRX HAS BEEN WAITING 23 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 37 page no 3 n bits 96 index PRIMARY of table
 `demo`.`lockt` trx id 8181 **lock_mode X waiting**
Record lock, heap no 17 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
 0: len 4; hex 8000000a; asc     ;;
 1: len 6; hex 000000001fed; asc       ;;
 2: len 7; hex b3000001270110; asc     '  ;;
 3: len 4; hex 8000000a; asc     ;;
 4: len 4; hex 80000019; asc     ;;

事务
------------
Trx id计数器8182
清除trx的n:o<8177撤消n:o<0状态:运行但空闲
历史记录列表长度3
每个会话的事务列表:
---事务处理281479682839888,未启动
0个锁结构,堆大小1136,0个行锁
---事务281479682837176,未启动
0个锁结构,堆大小1136,0个行锁
---事务281479682838984,未启动
0个锁结构,堆大小1136,0个行锁
---事务8181,活动45秒获取行
mysql表正在使用1,已锁定1
锁等待3个锁结构,堆大小1136,6行锁
MySQL线程id 25,操作系统线程句柄123145535516672,查询id 3434本地主机根目录更新
更新锁组col2=111,其中col2>33
-------TRX已等待23秒,等待授予此锁:
记录锁定空间id 37第3页n位96索引主表
`演示`.`lock`trx id 8181**lock\u模式X等待**
记录锁,堆号17物理记录:n_字段5;压缩格式;信息位0
0:len 4;十六进制800000A;asc;;
1:len 6;十六进制00000000 1FED;asc;;
2:透镜7;六角B300000170110;asc';;
3:透镜4;六角800000A;asc;;
4:len 4;hex 8000019;asc;;
这是会话2中的一个X锁起因update语句,而不是会话1中的查询语句

我们还可以用col2更新数据,使其大于更大的值(col2>200), 或者使用小范围(col2>32和col2<36)更新数据,以减小x锁的范围

    mysql> update lockt set col2=36 where col2 > 33 and col2 < 36;
    Query OK, 0 rows affected (0.00 sec)
    Rows matched: 0  Changed: 0  Warnings: 0
    
    mysql> update lockt set col2=35 where col2 > 32 and col2 < 36;
    Query OK, 1 row affected (0.00 sec)
    Rows matched: 1  Changed: 1  Warnings: 0
    
    mysql> update lockt set col2=35 where col2 > 200;
    Query OK, 2 rows affected (0.00 sec)
    Rows matched: 2  Changed: 2  Warnings: 0
    
    mysql> update lockt set col2=35 where col2 > 33;
    ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql>upda