我有一个基本上是先进先出队列的数据库表。行被系统的其他部分简单地插入到表中而被遗忘。每 5 分钟运行一次作业以处理队列中的项目。要处理的每一行的状态字段都从待处理值更改为处理值。队列中的后续重复项被匹配并标记为正在处理的较早排队项目的重复项。除了盲目插入行的系统部分之外,队列处理器作业是唯一对表做任何事情的事情。
这正是处理器对队列所做的事情:
START TRANSACTION;
SELECT id
FROM api_queue
WHERE status=:status_processing
-- Application checks this result set is empty, then...
UPDATE api_queue qs
INNER JOIN api_queue qdupes ON qdupes.products_id=qs.products_id AND qdupes.action=qs.action
SET qdupes.status = IF(qs.id=qdupes.id, :status_processing, :status_processing_duplicate)
WHERE qs.id IN (:queue_ids) ;
COMMIT;
-- Each queue item is processed
-- Once processing is complete, we purge the queue
START TRANSACTION;
SELECT COUNT(*) AS total FROM api_queue WHERE status = :status_processing ;
-- Application sanity checks the number of processing items it's about to delete against how many it's processed, and then...
DELETE FROM api_queue WHERE status IN (:status_processing, :status_processing_duplicate) ;
COMMIT;
在典型的 5 分钟内,队列将积压大约 100 项,但如果目录中发生大量更改,有时可能会达到数千项。
当第一个事务没有遇到死锁(0.1 - 0.2 秒完成)时,它通常非常快,但它似乎确实有大约 10% 的时间会遇到死锁。
为什么它经常遇到死锁?即使事务锁定了表中当前的所有行,我是否应该期望这会在向表中添加新行时引起争用?如果是这样,那是为什么?
我还注意到,有时上面的第一笔交易(包含UPDATE
查询)似乎根本不适用——尽管我认为这很可能是一个不相关的错误。
我的队列表如下所示:
CREATE TABLE IF NOT EXISTS `api_queue` (
`id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`products_id` int(11) NOT NULL,
`action` tinyint(3) NOT NULL,
`triggered_by` tinyint(3) NOT NULL,
`status` tinyint(1) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;