【问题标题】:A puzzled MySQL deadlock caused by two update..in两个update..in引起的困惑的MySQL死锁
【发布时间】:2020-12-31 10:08:39
【问题描述】:

我们在 MySQL 5.7 上遇到了令人困惑的死锁(引擎:InnoDB,隔离级别:RR)。 show engine innodb status的报告结果如下图

*** (1) TRANSACTION:
TRANSACTION 1739954050, ACTIVE 0 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 5 lock struct(s), heap size 1136, 4 row lock(s)
MySQL thread id 4253877, OS thread handle 47904135608064, query id 4259685238 jacky Searching rows for update

        UPDATE fruit_setting set
        value = CASE
            WHEN eid = 'L1XSHY' and `key` = 'priority' THEN '14343'
            WHEN eid = 'Rtb95t' and `key` = 'priority' THEN '14344'
            WHEN eid = 'wdsNwr' and `key` = 'priority' THEN '14345'           
            WHEN eid = 'K1Ikqy' and `key` = 'priority' THEN '14345'
            WHEN eid = 
            
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 533 page no 65378 n bits 0 index PRIMARY of table `jacky`.`fruit_setting` trx id 1739954050 lock_mode X locks rec but not gap waiting
...


*** (2) TRANSACTION:
TRANSACTION 1739954049, ACTIVE 0 sec fetching rows
mysql tables in use 1, locked 1
LOCK WAIT 94 lock struct(s), heap size 1136, 184 row lock(s)
MySQL thread id 4257460, OS thread handle 47904340621056, query id 4259685231  jacky Searching rows for update

        UPDATE fruit_setting set value = CASE
            WHEN eid = 'M5ecaA' and `key` = 'priority' THEN '16171'
            WHEN eid = '1o0bdT' and `key` = 'priority' THEN '16172'
            WHEN eid = 'XNxx5S' and `key` = 'priority' THEN '16173'
            WHEN eid = '1o0bdT' and `key` = 'priority' THEN '16174'
            WHEN eid = 
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 533 page no 65378 n bits 0 index PRIMARY of table `jacky`.`fruit_setting` trx id 1739954049 lock_mode X locks rec but not gap
Record lock, heap no 105 PHYSICAL RECORD: n_fields 10; compact format; info bits 0
 ...

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 533 page no 46944 n bits 0 index PRIMARY of table `jacky`.`fruit_setting` trx id 1739954049 lock_mode X locks rec but not gap waiting
Record lock, heap no 58 PHYSICAL RECORD: n_fields 10; compact format; info bits 0
...

上面报告中的sqls被截断了(可能由于MySQL的大小限制),整个sql中的一个看起来像(我们只记录准备好的语句)

UPDATE fruit_setting set value = CASE
            WHEN eid = ? and `key` = ? THEN ?
            WHEN eid = ? and `key` = ? THEN ?
            WHEN eid = ? and `key` = ? THEN ?
            WHEN eid = ? and `key` = ? THEN ?
            WHEN eid = ? and `key` = ? THEN ?
            WHEN eid = ? and `key` = ? THEN ?
            WHEN eid = ? and `key` = ? THEN ?
            WHEN eid = ? and `key` = ? THEN ?
            WHEN eid = ? and `key` = ? THEN ?
        END
WHERE aid = ? and eid in (?, ?, ?, ?, ?, ?, ?, ?, ?) and `key` = ?

表 DDL

CREATE TABLE `fruit_setting` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `aid` varchar(32) NOT NULL,
  `eid` varchar(32) NOT NULL,
  `key` varchar(32) NOT NULL,
  `value` varchar(32) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `i_eid_key` (`eid`, `key`),
  KEY `i_aid_key` (`aid`, `key`),
  KEY `i_aid_eid` (`aid`, `eid`)
);

注意,两个sql中的aidkey是相同的,in子句中的eid可以重叠;因此我们猜测死锁的发生是因为在索引i_eid_keyi_aid_eid 上以相反的顺序获取锁。

问题 1:为什么锁不是在二级索引 i_eid_keyi_aid_eid 上等待/保持,而是在主索引上? AFAIK,如果搜索更新使用二级索引,二级索引将在主索引之前被锁定。

如果在搜索中使用二级索引并且要设置的索引记录锁是独占的,InnoDB 也会检索相应的聚集索引记录并对其设置锁。

https://dev.mysql.com/doc/refman/8.0/en/innodb-locks-set.html

问题2:MySQL在执行@时是否根据in子句中出现的eid的顺序来一一获取索引记录的锁? 987654333@?

顺便说一句,死锁很难重现,我尝试以不同的顺序锁定eid,但是,新的死锁报告显示锁定在二级索引上。

【问题讨论】:

  • 与您的问题无关 - 但不要将您的 ? 占位符放在引号内。然后它们不再是参数占位符,它们只是恰好是 '?' 的文字字符串。
  • 这个表有第二个唯一键吗?索引的锁获取应该是原子的,即不是逐行的。但是我见过这样的情况:表的第二个 UNIQUE KEY 上的锁 not 与主键上的锁同时获得。似乎存在竞争条件,两个查询可能会以这种方式陷入僵局。示例:bugs.mysql.com/bug.php?id=86812
  • 是的,有一个辅助唯一键和一个 PK。索引记录上的锁获取应该是原子的,但是由于in子句中有多个eid,我猜整体不是原子的。
  • 对。在这些情况下,我告诉我公司的开发人员他们有选择:(1)删除辅助唯一键(或者删除主键并将以前的唯一键作为主键),(2)使用@987654337 @ 使每个线程在更新表之前获取原子表锁,或者(3)通过使用临界区或其他东西从单个应用程序线程运行更新。最终选择:通过处理应用程序中的异常并重新尝试更新来处理死锁。
  • 我认为bugs.mysql.com/bug.php?id=86812 的情况与我的不同。 86812中的死锁是因为会话2在会话1中删除后对二级唯一索引记录持有S模式锁,因此会话1中的后者插入触发了死锁。

标签: mysql innodb deadlock database-deadlocks


【解决方案1】:

对于问题 1:

经过一番讨论和思考,我们认为死锁可能是这样发生的:两个sql中至少有两个相同的extension_ids

  1. InnoDB 首先锁定两个不同的二级索引,例如i_eid_keyi_aid_key

  2. 然后锁定PK记录,顺序相反,最终导致死锁。

对于问题 2

MySQL 优化将对in clause 中的(常量)值进行排序

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-21
    • 2010-10-31
    • 1970-01-01
    • 1970-01-01
    • 2021-06-17
    相关资源
    最近更新 更多