【问题标题】:how the gap lock work in the case of share lock mode and update?在共享锁模式和更新的情况下,间隙锁如何工作?
【发布时间】:2020-09-02 19:42:01
【问题描述】:

前提条件:

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 lockt where col2=25 lock in share mode”将申请共享锁,并在范围(9, 25],(25,32],但是范围(33,+∞]超出了这些范围,为什么第二笔交易仍然被阻止,这超出了我的预期。我很困惑为什么会这样。有什么特别的吗指出我误解的间隙锁定?任何可以帮助我解决此问题的人将不胜感激。

【问题讨论】:

  • 几年前我问过MariaDB dev 的一个类似问题,这个问题有一个很好的解释。
  • 建议包含show engine innodb status 中包含查询拥有的锁的部分。也许我的问题略有不同。

标签: mysql


【解决方案1】:

同意@Solarflare

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

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     ;;

这是会话 2 中的更新语句导致的 X 锁定,而不是会话 1 中的查询语句。

我们也可以用 col2 更新数据超过一个更大的值(col2 > 200), 或者更新小范围的数据(col2 > 32 and col2

    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

【讨论】:

    【解决方案2】:

    添加一些关于锁的背景知识(并使这个答案比预期的要长一点),重要的是要理解,一般来说,MySQL 只会考虑在执行查询时锁定行/间隙。

    所有其他行/间隙都不相关:如果第二个查询既不查看相同的行/间隙,也不会对这些行/间隙进行更改(例如,通过添加一行),它不能 改变第一个查询的结果,无论第一个查询做什么,也不能改变第二个查询的结果。

    因此,要研究锁,了解 MySQL 查看哪些行绝对至关重要。 (如果锁实际上发生冲突将取决于查询和隔离级别,并且是您尝试尝试的内容,但实际上并不是此答案的一部分,因为这不是您感到困惑的原因。)

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

    这就是你所期望的:MySQL 找到带有 col2 = 25 的行,将其锁定(在索引和主键中),然后应该寻找带有 col2 &gt; 33 的行,并且不应该寻找那些使用索引的行发生冲突。

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

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

    显然,对于col2 &gt; 33,MySQL 决定这样做。既然这样做了,它查看的行现在是所有行。这将与col2 = 25 上的锁相冲突(它已使用 col2 上的索引锁定在主键中)。这不是因为间隙锁(您正在尝试调查),而是一个简单的普通旧锁定行。

    您可以使用更大的值重试查询,然后 MySQL 可能会决定使用该索引。您可以通过运行explain update lockt ... 来检查 MySQL 使用的索引,根据您的评论,临界值(带有您的特定数据)似乎是col2 &gt; 144。为此,执行计划应该显示使用col2 上的索引(key 列中的值),而对于col2 &gt; 143,它应该使用主键。

    您实际上可以强制 MySQL 使用您希望它使用的索引(按照您期望的方式锁定),并带有类似的索引提示

    update lockt force index (col2_ind) set col2=66666 where col2 > 33;
    

    再次强调:MySQL 不必必须锁定它查看的所有行和/或间隙,如果未使用(例如,如果它们未更新),它可以释放锁,而不是所有锁与所有锁冲突。有关这方面的详细信息将取决于查询和隔离级别,并将允许 MySQL 进行广泛的实际锁定行为。

    所以简短地回答您的问题:MySQL 锁基于索引,所以如果您尝试使用锁,请务必检查索引。

    【讨论】:

    • 首先感谢您的及时关注。但我仍然很困惑为什么用更大的值(例如 col2 > 143 )查询被阻止,但是,用值查询(例如 col2 > 144 )没有被阻止,似乎间隙锁是在 [25 的范围内添加的, 144],你明白我的意思吗?我想我已经把我的问题说清楚了,期待你的下一个答复。
    • 嗯,我想我当时的回答还不够清楚。您面临的影响不是由于间隙锁,而是因为使用了不同的索引。我会试着详细说明一下。
    • 如果这个版本不够详细,我得写一本书了。我将尝试再次延伸重点,然后请重新阅读我的解释(并可能提出一个具体问题):您的问题/困惑与间隙锁或间隙锁的工作原理有关,所以试着暂时忘记间隙锁!发生这种情况是因为 MySQL 对 col2 &gt; value 的 2 个不同值使用了不同的索引,并且其中只有一个会锁定您认为会锁定的行/间隙,而另一个不会。在这一点上确实需要为您点击,但我不知道我还能做些什么来让您到达那里。
    • mysql版本是5.7.31,可以按照上面说的方式复制。
    猜你喜欢
    • 1970-01-01
    • 2013-11-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-01
    • 2018-05-16
    相关资源
    最近更新 更多