首先,我不明白我怎么能得到any根本没有死锁,因为我没有使用显式锁定,所以只涉及一个表,每个进程都有一个单独的进程来插入、选择和更新行,一次只插入或更新一行,并且每个进程很少(也许一分钟一次)完全运行。
这是一个电子邮件队列:
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 秒一次,向表中执行一次插入,将状态设置为“待处理”。
有一个监控过程,每分钟检查“待处理”和“失败”电子邮件的数量是否过多。运行时间不到一秒钟,从来没有给我带来任何麻烦。
每分钟,发送进程都会抓取所有待处理的电子邮件。它一次循环一封电子邮件,将其状态设置为“处理中”,尝试发送它,最后将其状态相应地设置为“已发送”、“已丢弃”(它有理由决定电子邮件不应发送) ),或“失败”(被 SMTP 系统拒绝)。
设置状态的语句不寻常。
UPDATE emails_queue SET status=?, status_time=NOW() WHERE id=? AND status = ?
也就是说,只有当前状态已经是我认为的状态时,我才会更新状态。在此机制之前,我不小心启动了两个发送进程,它们每个进程都会尝试发送相同的电子邮件。现在,如果发生这种情况,一个进程会成功地将电子邮件从“待处理”移动到“处理中”,但第二个进程会更新零行,意识到存在问题,并跳过该电子邮件。
问题是,大约百分之一的情况是更新完全失败!我明白了com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
WTH?
这是发生这种情况的唯一表和唯一查询,并且仅发生在生产中(以最大限度地提高调查它的难度)。
唯一看起来不寻常的两件事是 (1) 更新参与 WHERE 子句的列,以及 (2) (未使用的)自动更新 status_time。
我正在寻找任何建议或诊断技术。