【问题标题】:MySQL Deadlock using an index with a new valueMySQL死锁使用具有新值的索引
【发布时间】:2019-06-03 21:42:29
【问题描述】:

表:

create table properties
(
  id              int auto_increment primary key,
  other_id        int          null
);

create index index_properties_on_other_id
  on properties (other_id);

TX 1:

start transaction;
SET @last_id = 1;
delete from `properties` WHERE `properties`.`other_id` = @last_id;
INSERT INTO `properties` (`other_id`) VALUES (@last_id);
commit

TX 2:

start transaction;
SET @last_id = 2;
delete from `properties` WHERE `properties`.`other_id` = @last_id;
INSERT INTO `properties` (`other_id`) VALUES (@last_id);
commit

假设在运行事务之前表是空的。

我的应用程序有 2 个用例。有时last_id 已经被另一行使用,因此它会被优先索引;但有时它会由先前的插入查询在同一个事务中生成,在这种情况下我会遇到死锁。

我需要在删除语句之后运行这两个事务。当我在 tx1 上运行 insert 时,它等待获得锁,然后我在 tx2 上运行 insert,tx2 出现死锁并回滚。

mysql            | LATEST DETECTED DEADLOCK
mysql            | ------------------------
mysql            | 2019-06-03 21:01:05 0x7f0ba4052700
mysql            | *** (1) TRANSACTION:
mysql            | TRANSACTION 320051, ACTIVE 12 sec inserting
mysql            | mysql tables in use 1, locked 1
mysql            | LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
mysql            | MySQL thread id 286, OS thread handle 139687839577856, query id 17804 172.18.0.1 root update
mysql            | INSERT INTO `properties` (`other_id`) VALUES (@last_id)
mysql            | *** (1) WAITING FOR THIS LOCK TO BE GRANTED:
mysql            | RECORD LOCKS space id 1524 page no 4 n bits 72 index index_properties_on_other_id of table `properties` trx id 320051 lock_mode X insert intention waiting
mysql            | Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
mysql            |  0: len 8; hex 73757072656d756d; asc supremum;;
mysql            | 
mysql            | *** (2) TRANSACTION:
mysql            | TRANSACTION 320052, ACTIVE 8 sec inserting
mysql            | mysql tables in use 1, locked 1
mysql            | 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
mysql            | MySQL thread id 287, OS thread handle 139687973168896, query id 17814 172.18.0.1 root update
mysql            | INSERT INTO `properties` (`other_id`) VALUES (@last_id)
mysql            | *** (2) HOLDS THE LOCK(S):
mysql            | RECORD LOCKS space id 1524 page no 4 n bits 72 index index_properties_on_other_id of table `properties` trx id 320052 lock_mode X
mysql            | Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
mysql            |  0: len 8; hex 73757072656d756d; asc supremum;;
mysql            | 
mysql            | *** (2) WAITING FOR THIS LOCK TO BE GRANTED:
mysql            | RECORD LOCKS space id 1524 page no 4 n bits 72 index index_properties_on_other_id of table `properties` trx id 320052 lock_mode X insert intention waiting
mysql            | Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
mysql            |  0: len 8; hex 73757072656d756d; asc supremum;;
mysql            | 
mysql            | *** WE ROLL BACK TRANSACTION (2)

删除语句后的锁状态:

mysql            | ---TRANSACTION 320066, ACTIVE 90 sec
mysql            | 2 lock struct(s), heap size 1136, 1 row lock(s)
mysql            | MySQL thread id 287, OS thread handle 139687973168896, query id 18076 172.18.0.1 root
mysql            | TABLE LOCK table `properties` trx id 320066 lock mode IX
mysql            | RECORD LOCKS space id 1524 page no 4 n bits 72 index index_properties_on_other_id of table `properties` trx id 320066 lock_mode X
mysql            | Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
mysql            |  0: len 8; hex 73757072656d756d; asc supremum;;
mysql            | 
mysql            | ---TRANSACTION 320065, ACTIVE 95 sec
mysql            | 2 lock struct(s), heap size 1136, 1 row lock(s)
mysql            | MySQL thread id 286, OS thread handle 139687839577856, query id 18039 172.18.0.1 root
mysql            | TABLE LOCK table `properties` trx id 320065 lock mode IX
mysql            | RECORD LOCKS space id 1524 page no 4 n bits 72 index index_properties_on_other_id of table ``properties` trx id 320065 lock_mode X
mysql            | Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
mysql            |  0: len 8; hex 73757072656d756d; asc supremum;;

所以两个事务正在删除/插入不同的other_ids,我没想到它们会陷入僵局。我想了解为什么会发生这种情况。

【问题讨论】:

  • 我想你可能想锁定相关的表。看到这个答案:stackoverflow.com/questions/40749730/…
  • 我认为你永远不应该明确锁定表。将隔离级别更改为“READ COMMITED”可能会有所帮助。

标签: mysql database innodb deadlock


【解决方案1】:

MySQL 不会锁定不存在的东西,例如锁定您没有删除的行。它也不存储您尝试删除具有特定值“1”的行。它的作用是标记“1”应该在的位置,如果它本来应该在那里,并用gap lock 锁定它,它具有以下特征:

InnoDB 中的间隙锁是“纯粹的抑制性”,这意味着它们的唯一目的是防止其他事务插入到间隙中。间隙锁可以共存。 一个事务采用的间隙锁不会阻止另一个事务在同一间隙上采用间隙锁。共享和独占间隙锁之间没有区别。它们彼此不冲突,并且执行相同的功能。

在空表中,1 所在的位置是“表中的任何位置”(或从开始到死锁中提到的“至上”的任何位置) - 因此被delete 锁定。 2 也是如此。而且这些锁根据定义不会相互冲突。

但是insert 可以。第一个insert 将不得不等待第二个事务为其删除发出的gaplock。如果第二个事务现在也尝试将insert 放入间隙,这将需要解除第一个事务的间隙锁,但这不可能发生,因为第一个事务已经等待第二个间隙锁被解除。所以你会陷入僵局。

一旦你填满了你的表,这种情况就会减少,因为间隙锁不再需要跨越整个表。如果你例如表中已经有other_id 1 和 3,删除/插入值 2 和 4 不会相互死锁。

一般来说,空表很少见,您不能也不应该从这种特殊情况推断出任何正常行为。你基本上必须接受边缘情况:

间隙锁是性能和并发性之间权衡的一部分

所以在一般用例中,您只需要准备好偶尔会发生死锁(然后重复事务)。如果您的预期用例是您有一个基本上是空的表,或者主要是在值的末尾添加,或者经常将 2 个值添加到同一个间隙中,您可能需要不同的解决方案(并且应该询问如何在这个特定的用例中继续)。你可以例如能够使用唯一索引(不需要间隙锁),重新编码/散列您的值以随机位于索引中,或者您让所有事务锁定您知道存在的东西,以便它们相互等待。

【讨论】:

  • 谢谢,这一切都说得通。我被状态输出中没有关键字“gap”的supremum gap lock吓跑了。但这只是另一个间隙锁。而且我没有意识到两个交易可以锁定完全相同的差距。给出的理由是:“允许冲突间隙锁的原因是,如果从索引中清除记录,则必须合并不同事务在记录上持有的间隙锁。”
  • 为了解决我的问题,我可以根据id 删除,而不是在非唯一索引other_id 上删除,这不应该锁定差距,对吧?
  • 如果 id 不存在并且在相同的间隙中,您将面临同样的问题,因为 mysql 无法存储您要删除的 id,就在它本来应该在的位置。但通常情况下,使用不同的索引可以将锁放在不同的位置,这可以缓解问题。正如我所说,如果这是一个真正的问题(例如,如果这实际上是您在正常使用时会遇到的场景),您可能需要添加更多详细信息/提出新问题以找到不同的解决方案(也许通过重新设计问题,表锁(见 LSernis 评论),通过接受和处理死锁,...)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-01-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-16
  • 2019-02-23
相关资源
最近更新 更多