MySQL间隙锁定推理

时间:2017-03-02 02:02:37

标签: mysql database-deadlocks

我遇到了僵局,我正试图找出其背后的原因。

问题可以归结为:

表:

create table testdl (id int auto_increment, c int, primary key (id), key idx_c (c));

隔离级别是可重复读取

(Tx1):begin; delete from testdl where c = 1000; -- nothing is deleted coz the table is empty

(Tx2):begin; insert into testdl (c) values (?);

无论Tx2的价值是什么,它都会挂起。所以它基本上意味着当delete from testdl where c = 1000找不到匹配时,Tx1保持整个范围(-∞,+∞)的间隙,对吗?

所以我的问题是:这是设计的吗?如果是这样的话有什么意义呢?

更新

假设我们已在testdl中有记录:

+----+------+
| id | c    |
+----+------+
|  1 | 1000 |
+----+------+

案例1:

(Tx1):select * from testdl where c = 500 for update; -- c = 500 not exists

(TX2):insert into testdl (c) values (?);

在这种情况下,可以插入任何值> = 1000,因此Tx1锁定间隙(-∞,1000)

再次,是否需要锁定(-∞,1000)?这背后的原因是什么?

2 个答案:

答案 0 :(得分:1)

执行后

create table testdl (id int auto_increment, c int, primary key (id), key idx_c (c));

-- transaction 1
begin;
delete from testdl where c = 1000;

-- transaction 2
begin;
insert into testdl (c) values (?); -- ? can be any int

select * from performance_schema.data_locks的输出是:

+--------+----------------------------------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+--------------------+-------------+------------------------+
| ENGINE | ENGINE_LOCK_ID                         | ENGINE_TRANSACTION_ID | THREAD_ID | EVENT_ID | OBJECT_SCHEMA | OBJECT_NAME | PARTITION_NAME | SUBPARTITION_NAME | INDEX_NAME | OBJECT_INSTANCE_BEGIN | LOCK_TYPE | LOCK_MODE          | LOCK_STATUS | LOCK_DATA              |
+--------+----------------------------------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+--------------------+-------------+------------------------+
| INNODB | 140043377180024:1073:140043381454544   |                  2322 |        48 |      218 | test          | testdl      | NULL           | NULL              | NULL       |       140043381454544 | TABLE     | IX                 | GRANTED     | NULL                   |
| INNODB | 140043377180024:12:5:1:140043381451552 |                  2322 |        48 |      218 | test          | testdl      | NULL           | NULL              | idx_c      |       140043381451552 | RECORD    | X,INSERT_INTENTION | WAITING     | supremum pseudo-record |
| INNODB | 140043377180872:1073:140043381460688   |                  2321 |        49 |      154 | test          | testdl      | NULL           | NULL              | NULL       |       140043381460688 | TABLE     | IX                 | GRANTED     | NULL                   |
| INNODB | 140043377180872:12:5:1:140043381457776 |                  2321 |        49 |      154 | test          | testdl      | NULL           | NULL              | idx_c      |       140043381457776 | RECORD    | X                  | GRANTED     | supremum pseudo-record |
+--------+----------------------------------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+--------------------+-------------+------------------------+

事务2等待获取插入意图锁(负无穷大,正无穷大),因为事务1持有下一键锁定(负无穷大,正无穷大),所以它无法进行。

但是执行后

create table testdl (id int auto_increment, c int, primary key (id), key idx_c (c));
insert into testdl values(1, 1000);

-- transaction 1
begin;
select * from testdl where c = 500 for update;

select * from performance_schema.data_locks的输出是:

+--------+----------------------------------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+-----------+-------------+-----------+
| ENGINE | ENGINE_LOCK_ID                         | ENGINE_TRANSACTION_ID | THREAD_ID | EVENT_ID | OBJECT_SCHEMA | OBJECT_NAME | PARTITION_NAME | SUBPARTITION_NAME | INDEX_NAME | OBJECT_INSTANCE_BEGIN | LOCK_TYPE | LOCK_MODE | LOCK_STATUS | LOCK_DATA |
+--------+----------------------------------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+-----------+-------------+-----------+
| INNODB | 140043377180872:1074:140043381460688   |                  2341 |        49 |      167 | test          | testdl      | NULL           | NULL              | NULL       |       140043381460688 | TABLE     | IX        | GRANTED     | NULL      |
| INNODB | 140043377180872:13:5:2:140043381457776 |                  2341 |        49 |      167 | test          | testdl      | NULL           | NULL              | idx_c      |       140043381457776 | RECORD    | X,GAP     | GRANTED     | 1000, 1   |
+--------+----------------------------------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+-----------+-------------+-----------+

事务1保持间隙锁定(负无穷(1000,1)),因此其他事务无法将数据插入该间隙。

答案 1 :(得分:0)

这类似于我最近对自己感到好奇,所以让我尝试解释一下...

  

无论Tx2中的值是多少,它都会挂起。因此,从c = 1000的testdl删除找不到匹配项时,对Tx1来说,它基本上占据了整个范围的间隙(-∞,+∞)。

     

所以我的问题是:这是设计使然吗?如果是这样,这有什么意义?

这是设计使然,间隙锁定的主要目的是防止任何记录插入到这些间隙中,以避免phantom rows

因此,假设您有一个空表,并且在事务内部执行delete from testdl where c = 1000;。现在,无论您希望在此查询之后在表中没有这样的行之前存在多少这样的行,对吗?因此,如果之后您在同一事务中执行select * from testdl where c = 1000 for update;,则您希望它是空结果。

但是为了确保没有新行插入c = 1000到表中,我们需要锁定可以插入此类记录的间隙。并且在一个空表中只有一个间隙:最小伪记录与最高伪记录之间的间隙(如Michael所指出的那样)。

  

在这种情况下,可以插入任何> = 1000的值,因此Tx1会锁定间隙(-∞,1000)

     

再次,是否需要锁定(-∞,1000)?背后的原因是什么?

我相信上面的解释也应该解释当表中已经有一个记录时,您对第二种情况提出的问题。但是我还是会尽力解释。

在您的第一笔交易中,您进行了select * from testdl where c = 500 for update;,现在,如果我们决定在此交易中再次进行此类查询,则需要确保没有出现带有c = 500的新记录。因此,我们需要锁定所有必要的间隙。我们有哪些差距? (-∞, 1000)(1000, +∞),显然是新记录,其中c = 500不会插入第二个间隙,但它们会插入第一个间隙,因此我们必须将其锁定。

希望这能回答它。