解释无法解释的僵局

Mal*_*lio 13 mysql

首先,我没有看到我如何得到任何死锁,因为我没有使用显式锁定,只涉及一个表,每个插入,选择和更新行都有一个单独的进程,只有一行一次插入或更新,每个进程很少(可能每分钟一次)运行.

这是一个电子邮件队列:

CREATE TABLE `emails_queue` (
  `id` varchar(40) NOT NULL,
  `email_address` varchar(128) DEFAULT NULL,
  `body` text,
  `status_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `status` enum('pending','inprocess','sent','discarded','failed') DEFAULT NULL,
  KEY `status` (`status`),
  KEY `status_time` (`status`,`status_time`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 
Run Code Online (Sandbox Code Playgroud)

生成过程响应一些用户操作但大约每90秒执行一次插入表,将状态设置为"挂起".

有一个监控过程,每分钟检查"待处理"和"失败"电子邮件的数量是否过多.它运行不到一秒钟,从未给我任何麻烦.

每分钟,发送过程都会抓取所有待处理的电子邮件.它一次循环播放一封电子邮件,将其状态设置为"inprocess",尝试发送,最后将其状态设置为"已发送","丢弃"(它有理由决定不发送电子邮件) ),或"失败"(由SMTP系统拒绝).

设置状态的声明很不寻常.

UPDATE emails_queue SET status=?, status_time=NOW() WHERE id=? AND status = ?
Run Code Online (Sandbox Code Playgroud)

也就是说,我只更新状态,如果当前状态已经是我认为的那样.在此机制之前,我意外地启动了两个发送过程,他们每个都会尝试发送相同的电子邮件.现在,如果发生这种情况,一个进程将成功地从"待定"到"进程内"移动电子邮件,但第二个将更新零行,实现有一个问题,跳过电子邮件.

问题是,大约一次在100,更新完全失败!我明白了com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction

WTH?

这是唯一一个只发生这种情况的表和查询,它只发生在生产中(最大限度地调查它的难度).

只有两件看起来不寻常的事情是(1)更新参与WHERE子句的列,以及(2)status_time的(未使用的)自动更新.

我正在寻找任何建议或诊断技术.

Mar*_*rkR 14

首先,死锁不依赖于显式锁定.MySQL的LOCK TABLE或使用非默认事务隔离模式不需要具有死锁.如果您从未使用过显式事务,则仍然可能存在死锁.

死锁可以很容易地在一张桌子上发生.最常见的是它来自一个热桌.

如果所有事务只执行单行插入,甚至可能发生死锁.

如果你有,就会发生僵局

  • 多个与数据库的连接(显然)
  • 内部涉及多个锁的任何操作.

不显而易见的是,大多数情况下,单行插入或更新涉及多个锁.原因是在插入/更新期间还需要锁定二级索引.

SELECT不会锁定(假设您使用的是默认隔离模式,并且没有使用FOR UPDATE),因此它们不能成为原因.

SHOW ENGINE INNODB STATUS是你的朋友.它将为您提供一堆(无可否认的)非常复杂的死锁信息,特别是最新的死锁信息.

  • 你不能完全消除死锁,它们将继续在生产中发生(即使在测试系统上如果压力正确)
  • 瞄准非常少量的死锁.如果1%的交易死锁,那可能太多了.
  • 如果您完全理解这些含义,请考虑将事务的事务隔离级别更改为读取提交
  • 确保您的软件正确处理死锁.

  • `SHOW ENGINE INNODB STATUS`做到了.有一个几乎无关(并且完全被遗忘)的报告过程干扰了更新. (3认同)