解释无法解释的僵局

时间:2011-05-11 20:28:22

标签: 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 

生成过程响应一些用户操作但大约每90秒执行一次插入表格,将状态设置为“待定”。

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

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

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

UPDATE emails_queue SET status=?, status_time=NOW() WHERE id=? AND status = ?

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

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

WTH?

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

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

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

3 个答案:

答案 0 :(得分:14)

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

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

如果你的所有交易只进行一行插入,就会发生甚至的死锁。

如果你有

,就会发生死锁
  • 与数据库的多个连接(显然)
  • 内部涉及多个锁定的任何操作。

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

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

SHOW ENGINE INNODB STATUS是你的朋友。它会给你一堆(无可否认的)非常复杂的死锁信息,特别是最新的死锁信息。

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

答案 1 :(得分:0)

对于某些数据库服务器,存在锁定行为的默认设置。通常默认使用锁(至少在我使用的系统上)。我不确定这在mysql上是否正确,但我相信它是。

您在emails_queue表上有索引吗?索引的类型可以改变它的锁定方式。在一个案例中,我处理了没有表上的聚集索引导致它 使用页面锁定而不是行锁定。我明确告诉它使用行锁定和 它默默地改变了它。页面锁定可能导致死锁。尝试检查该索引。

如果这些没有帮助,则解决方案是错误消息中建议的解决方案。抓住 死锁的异常,并在sql发生时重新运行。

答案 2 :(得分:0)

您尚未描述说明中的交易范围。如果您描述的每个进程都尝试在单个事务中执行所有操作,那么此系统中肯定存在死锁的可能性。

虽然看起来不会发生死锁,因为只涉及一个表,但被锁定的资源不是表而是行。如果使用相同的事务来操纵多个行,则两个进程可能各自持有其他进程所需的行锁。