【问题标题】:MySQL deadlock caused by concurrent INSERT and SELECT并发 INSERT 和 SELECT 导致的 MySQL 死锁
【发布时间】:2015-12-24 06:49:39
【问题描述】:
  • MySQL 版本:5.6
  • 存储引擎:InnoDB

当两个任务尝试select 然后insert 同一个表时发生死锁。过程如下:

          Task_1       Task_2
          ------      ------
Phase 1 | SELECT      SELECT
Phase 2 | INSERT      INSERT

SELECT count(id) from mytbl where name = 'someValue' and timestampdiff(hour, ts, now()) < 1;
INSERT mytbl (id, name, ts) values ('newId', 'anotherValue', now());

死锁日志如下(部分细节被截断):

------------------------
LATEST DETECTED DEADLOCK
------------------------
151225  8:22:17
*** (1) TRANSACTION:
TRANSACTION 0 746402, ACTIVE 0 sec, process no 4690, OS thread id 140411390486272 inserting
mysql tables in use 1, locked 1
LOCK WAIT 1172 lock struct(s), heap size 112624, 32914 row lock(s)
MySQL thread id 3909, query id 31751474 10.20.36.38 mydb update
INSERT INTO mytbl -- truncated
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 0 page no 5044 n bits 88 index `PRIMARY` of table `MYDB`.`mytbl` trx id 0 746402 lock_mode X insert intention waiting
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; asc supremum;;

*** (2) TRANSACTION:
TRANSACTION 0 746449, ACTIVE 0 sec, process no 4690, OS thread id 140411389953792 inserting, thread declared inside InnoDB 500
mysql tables in use 1, locked 1
1172 lock struct(s), heap size 112624, 32914 row lock(s)
MySQL thread id 3906, query id 31751477 10.20.36.38 mydb update
INSERT INTO mytbl  -- truncated
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 0 page no 5044 n bits 88 index `PRIMARY` of table `MYDB`.`MYTBL` trx id 0 746449 lock mode S
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; asc supremum;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 0 page no 5044 n bits 88 index `PRIMARY` of table `MYDB`.`MYTBL` trx id 0 746449 lock_mode X insert intention waiting
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; asc supremum;;

*** WE ROLL BACK TRANSACTION (2)

问题

  1. 根据 MySQL 手册,简单的SELECT 语句使用不需要S 锁 的快照读取。 INSERT 语句需要X lock 在要插入的单行上。那为什么Task_2持有S锁导致死锁?

编辑

SHOW CREATE TABLE的结果如下:

| task_content | CREATE TABLE `mytbl` (
`id` bigint(20) NOT NULL,
`ts` timestamp NULL DEFAULT NULL,
`name` varchar(32) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |

【问题讨论】:

  • 您可以将事务的隔离级别更改为可序列化。但通常最好的做法是尽可能避免在事务中使用selects
  • 这里有一些关于死锁的有趣信息:dev.mysql.com/doc/refman/5.7/en/innodb-deadlocks.html
  • 交易来源是否相同?
  • @MuhammadMuazzam 不,这两个事务是由 IBATIS API 生成的两个不同会话调用的。
  • 你能在同一个地方测试它吗?然后发生了什么?

标签: mysql deadlock


【解决方案1】:

文章here 对锁和隔离级别进行了详尽的解释。

感谢@newtover 提供有关隔离级别的线索。我对这篇文章的总结和我自己的问题的答案如下:

InnoDB 中的默认隔离级别是可重复读取,它会锁定索引(不锁定数据表)直到事务结束

在我的情况下,唯一的索引是PRIMARY,这在我的SELECT查询中没有用(可以通过explain select...进行验证)。结果,PRIMARY 索引中的所有 条目都被锁定。当TXN_2 等待某个条目上的X 锁 时,该条目被TXN_1 保留的S 锁 锁定。同样,TXN_1 等待另一个条目上的 X 锁,但该条目也被自己保留的 S 锁 锁定。发生"one S two X" 死锁。

相比之下,在我在name 列上创建索引name 后,索引name 将在SELECT 语句中使用(可以通过explain select ... 验证),所以锁将是在索引 name 而不是 PRIMARY 上发布。更重要的是,SELECT 语句只会对等于someValue 的条目发出S 锁,而不是索引name 的所有条目。此外,INSERT 所需的 IX 锁X 锁 将在索引PRIMARY 上发布。 S锁IX锁冲突,X锁会解决。

name 列上的索引不仅加快了查询速度,更重要的是阻止了锁定索引的所有条目。

【讨论】:

  • 有时解决方案就是这么简单。我已经在我用来执行位置的列上创建了一个索引,现在一切都很好。谢谢。
【解决方案2】:

其中 name = 'someValue' 和 timestampdiff(hour, ts, now())

那是相当低效的。让我们清理它以加快速度,减少死锁的可能性。

timestampdiff(hour, ts, now()) &lt; 1 隐藏任何带有ts 的索引;让我们把它改写成

ts < NOW() - INTERVAL 1 HOUR

你的以意想不到的方式被截断;我的说“ts 早于 1 小时前”,我怀疑你想要的。

现在我们可以索引ts,效果很好。但是,让我们通过使用“复合”索引来更进一步:

INDEX(name, ts)

这将有效地使用WHERE 子句的两个部分来定位行。

你说COUNT(id)——这意味着你需要在id中避免NULLs。也许这不是问题,您可以简单地说COUNT(*)

那些应该使SELECT 更快。现在让我们弄清楚为什么SELECTINSERT 彼此有任何关系。他们在同一个交易中吗?或者你是否关闭了自动提交,但忘了说COMMIT?请向我们展示整个交易,以及SHOW CREATE TABLE

【讨论】:

  • 根据死锁日志,SELECTINSERT 在同一个事务中,这正是我的问题。 autocommit 选项为 ON
  • 索引建议是减少“32914 行锁”(除其他外)。
  • 两个交易中的“someValue”是否相同?
  • 我还有其他问题最好由SHOW CREATE TABLE回答。
  • 我在问题中附加了SHOW CREATE TABLE。我在 name 列上创建了一个索引,这已将行锁的数量减少到 1。someValue 在不同的事务中是不同的。
【解决方案3】:

如果您当前的隔离级别是repeatable read 或更高,为了能够在事务中为select count(id) ... 重复相同的结果,MySQL 必须锁定整个主键(或WHERE 使用的另一个键的一部分)健康)状况)。然后通过插入新值来修改密钥。但是并发事务修改了密钥的状态,这已经被看到了。两者都可以从键的相同状态开始,然后等到另一个完成且没有更改,以便应用自己的更改。

【讨论】:

  • 在 InnoDB 中,SELECT 不应该是没有共享锁的一致读取(即快照读取)吗?
【解决方案4】:

BEGINEND事务中写下你的每一个查询。我希望它不会发生。

更多:here

【讨论】:

    【解决方案5】:

    查询的书面部分似乎是正确的,并且绝对不是问题的根源。我猜您的任务正在执行交错,并且在开始时,每个任务都会启动一个事务。您没有说如何执行这些任务以及执行每个任务时的主键值是什么? 您可能希望将主键字段更改为 AUTO_INCREMENT 或确保您的任务使用的主键确实是唯一的。
    如果它没有帮助,另一种(但不建议)解决方案是通过 mutext 在上层保护您的过程调用代码。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-22
      • 1970-01-01
      • 2022-11-03
      • 1970-01-01
      • 2011-03-18
      • 2013-04-05
      相关资源
      最近更新 更多