由于多线程插入导致 MySQL 死锁

Posted

技术标签:

【中文标题】由于多线程插入导致 MySQL 死锁【英文标题】:Deadlock in MySQL due to Insert by multiple threads 【发布时间】:2015-09-10 09:48:06 【问题描述】:

我有一个多线程应用程序,它尝试在多个批次中 INSERT 表中的一条记录。每个线程处理一批。有时我会遇到死锁错误,以下是跟踪。

我要插入记录的表是这样的:

RecordBase (Col1, Col2, Col3)

Col1Col2 共同构成一个复合主键。

我之前认为这可能是由于index-record 锁定,但跟踪清楚地表明,相互阻塞的语句没有任何重复记录。那么为什么会导致死锁呢?

------------------------ LATEST DETECTED DEADLOCK ------------------------ 
 2015-09-09 17:13:22 2b70324de700 

 *** (1) TRANSACTION: 
 TRANSACTION 1787379600, ACTIVE 7 sec inserting mysql tables in use 1, locked 1 LOCK WAIT 486 lock struct(s), heap size 63016, 13085 row lock(s), undo log entries 8713 MySQL thread id 537443, OS thread handle 0x2b703286c700, query id 578560605 127.0.0.1 192.168.1.195 demoreleaseroot update 
 INSERT INTO Record_Base VALUES 
 ('da5fd95c-4d8e-11e5-9761-22000bd9028a','101e7dcd-4c81-11e5-9ca0-22000bd8028c','0'),
 ('da5fcf08-4d8e-11e5-9761-22000bd9028a','101e7dcd-4c81-11e5-9ca0-22000bd8028c','0'),
 ('da5fc4eb-4d8e-11e5-9761-22000bd9028a','101e7dcd-4c81-11e5-9ca0-22000bd8028c','0'),
 ('da5fbabe-4d8e-11e5-9761-22000bd9028a','101e7dcd-4c81-11e5-9ca0-22000bd8028c','0'),
 ('da5fb087-4d8e-11e5-9761-22000bd9028a','101e7dcd-4c81-11e5-9ca0-22000bd8028c','0'),
 ('da5fa616-4d8e-11e5-9761-22000bd9028a','101e7dcd-4c81-11e5-9ca0-22000bd8028c','0'),
 ('da5f99bf-4d8e-11e5-9761-22000bd9028a','101e7dcd-4c81-11e5-9ca0-22000bd8028c','0'),
 ('da5f8f0f-4d8e-11e5-9761-22000bd9028a','101e7dcd-4c81-11e5-9ca0-22000bd8028c','0'),
 ('da5f5e2e-4d8e-11e5-9761-22000bd9028a','101e7dcd-4c81-11e5-9ca0-22000bd8028c','0'),
 ('da5f52e3-4d8e-11e5-9761-22000bd9028a','101e7d 

 *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 26232190 page no 5961 n bits 160 index `PRIMARY` of table `provalant101_mxradon`.`Record_Base` trx id 1787379600 lock_mode X locks gap before rec insert intention waiting Record lock, heap no 29 PHYSICAL RECORD: n_fields 5; compact format; info bits 0  0: len 30; hex 65376566306364332d353039352d313165352d393736312d323230303062; asc e7ef0cd3-5095-11e5-9761-22000b; (total 36 bytes);  1: len 30; hex 31303165376463642d346338312d313165352d396361302d323230303062; asc 101e7dcd-4c81-11e5-9ca0-22000b; (total 36 bytes);  2: len 6; hex 00006a893f90; asc   j ? ;;  3: len 7; hex b40001a7c3290f; asc      ) ;;  4: len 4; hex 80000000; asc     ;;  *** 

 (2) TRANSACTION: TRANSACTION 1787379848, ACTIVE 1 sec inserting mysql tables in use 1, locked 1 1030 lock struct(s), heap size 112168, 5801 row lock(s), undo log entries 2639 MySQL thread id 537467, OS thread handle 0x2b70324de700, query id 578563042 127.0.0.1 192.168.1.195 demoreleaseroot update INSERT INTO Record_Base VALUES 
 ('4849f98e-5094-11e5-9761-22000bd9028a','101e7dcd-4c81-11e5-9ca0-22000bd8028c','0'),
 ('4849ebe5-5094-11e5-9761-22000bd9028a','101e7dcd-4c81-11e5-9ca0-22000bd8028c','0'),
 ('4849c44c-5094-11e5-9761-22000bd9028a','101e7dcd-4c81-11e5-9ca0-22000bd8028c','0'),
 ('4849add7-5094-11e5-9761-22000bd9028a','101e7dcd-4c81-11e5-9ca0-22000bd8028c','0'),
 ('4849a0ef-5094-11e5-9761-22000bd9028a','101e7dcd-4c81-11e5-9ca0-22000bd8028c','0'),
 ('48499430-5094-11e5-9761-22000bd9028a','101e7dcd-4c81-11e5-9ca0-22000bd8028c','0'),
 ('48498752-5094-11e5-9761-22000bd9028a','101e7dcd-4c81-11e5-9ca0-22000bd8028c','0'),
 ('48496d2d-5094-11e5-9761-22000bd9028a','101e7dcd-4c81-11e5-9ca0-22000bd8028c','0'),
 ('4848731e-5094-11e5-9761-22000bd9028a','101e7dcd-4c81-11e5-9ca0-22000bd8028c','0'),
 ('4846784e-5094-11e5-9761-22000bd9028a','101e7d 

 *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 26232190 page no 5961 n bits 152 index `PRIMARY` of table `provalant101_mxradon`.`Record_Base` trx id 1787379848 lock_mode X locks gap before rec Record lock, heap no 29 PHYSICAL RECORD: n_fields 5; compact format; info bits 0  0: len 30; hex 65376566306364332d353039352d313165352d393736312d323230303062; asc e7ef0cd3-5095-11e5-9761-22000b; (total 36 bytes);  1: len 30; hex 31303165376463642d346338312d313165352d396361302d323230303062; asc 101e7dcd-4c81-11e5-9ca0-22000b; (total 36 bytes);  2: len 6; hex 00006a893f90; asc   j ? ;;  3: len 7; hex b40001a7c3290f; asc      ) ;;  4: len 4; hex 80000000; asc     ;;  

 *** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 26232190 page no 14639 n bits 192 index `PRIMARY` of table `provalant101_mxradon`.`Record_Base` trx id 1787379848 lock_mode X locks gap before rec insert intention waiting Record lock, heap no 121 PHYSICAL RECORD: n_fields 5; compact format; info bits 0  0: len 30; hex 38393531613333352d353039342d313165352d393736312d323230303062; asc 8951a335-5094-11e5-9761-22000b; (total 36 bytes);  1: len 30; hex 31303165376463642d346338312d313165352d396361302d323230303062; asc 101e7dcd-4c81-11e5-9ca0-22000b; (total 36 bytes);  2: len 6; hex 00006a893f90; asc   j ? ;;  3: len 7; hex b40001a7c71c1c; asc        ;;  4: len 4; hex 80000000; asc     ;; 

 *** WE ROLL BACK TRANSACTION (2) 

【问题讨论】:

当更多事务持有锁请求时会发生死锁。在您的情况下,您有多个线程访问同一个表,因此预计会发生死锁。僵局不是惊慌的理由。 InnoDB 会自动检测它们并抛出错误。对于我们这些使用 MySQL 的开发人员来说,这意味着我们只需要重复查询以防出错。在一些伪代码中,它类似于while(true) if(do_query()) break; @Mjh 我做了同样的事情来使我的应用程序工作。我只是想详细了解造成这种僵局的原因。 死锁是因为你的线程同时访问同一张表(并发访问)。为了防止数据被覆盖,MySQL允许对表进行顺序访问,进行写操作。对于允许写入表的线程或进程,它必须获取锁。但是,当两个线程争相获取锁时 - 两者都不会获取它 - 这将陷入无限循环。为了打破这个循环,MySQL 会抛出错误并将其称为“死锁”。 TL;DR:您从多个线程到表的并发插入导致死锁。 (这是简化的解释) @Mjh 我认为这不是正确的解释。 Innodb 具有行级锁定,除非被访问的行相同,否则没有像顺序访问那样的东西。在插入的情况下,记录无论如何都是唯一的,因此行级锁可能不会导致死锁。我做了更多的研究,发现这个article完美地解释了这个问题 好的,如果你对另一个解释没意见,那么我不会插手。当你写的时候,你不能访问表,同时写 - 两件事不能写同时。如果没有要锁定的行,您也不能锁定行。您也无法安全地计算 auto_increment,除非您通过放置锁、递增、释放并让等待线程访问它来顺序访问计数器。但是,既然您找到了解释,那么我认为一切都很好:) 【参考方案1】:

这类死锁称为间隙锁。我发现这个post 很有帮助。

此外,您可以在Mysql Manual 中阅读有关间隙锁定的更多信息

【讨论】:

除了链接帖子中所说的之外,您可能会在没有唯一键约束的情况下对并发插入产生死锁,仅将自动生成的主键和另一个索引结合起来就可能导致问题.见dba.stackexchange.com/questions/86878/… 我写了这篇文章medium.com/@syl.fabre/…来解释我们公司是如何处理这个问题的 链接的文章位于不再解析的域中【参考方案2】:

由于 mysql 的机制,我的应用程序过去经常发生死锁。我用两种方法解决了它。首先,我将影响同一张表的批处理作业放在同一个线程中并按顺序运行它们,其次我在查询执行周围放置一个 try-catch 块以捕获死锁错误并让它尝试相同的查询执行 5 次以上,包括尝试之间的睡眠功能.

【讨论】:

由于“在尝试之间包含睡眠功能”部分而被赞成,我认为它很重要,而且经常被忽视。【参考方案3】:

我遇到了同样的问题,@Aashish 的回答向我解释了,所以如果你想了解为什么这对我有用,你需要阅读他的回答。

我的场景:

我在 Date 和 idSomething 列上有一个唯一索引(顺序很重要,Date 是第一个,idSomething 是第二个)

我有一个由 12 个线程组成的池,为每个 idSomething(大约 1500 个不同的 idSomething)插入相同的日期范围(3 天)。

修复:

将 UNIQUE 索引更改为列 idSomething (1st) 和 Date (2nd)。 然后这个过程不是以有序的方式处理“Somethings”,而是在订单中添加了一个随机播放,因此 idSomething I 和 I+1 不太可能同时被池化。 这样间隙锁就不会相互重叠,死锁也就消失了。

【讨论】:

能否详细说明,无法理解修复

以上是关于由于多线程插入导致 MySQL 死锁的主要内容,如果未能解决你的问题,请参考以下文章

Python多线程之死锁

多线程小结

python多线程queue导致的死锁问题

Java多线程导致数据库死锁(Java 7)

countdownlatch 导致的多线程死锁

多线程海量删除查询和批量插入批量的 SQL 死锁