我有一个基本上是先进先出队列的数据库表。行只是被系统的其他部分插入表中而被遗忘。每隔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
查询)似乎并没有真正适用 - 尽管我认为这可能是一个无关的错误。< / p>
我的队列表如下所示:
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 ;
答案 0 :(得分:0)
我的口头禅:&#34;不要排队,只是这样做&#34;。我这样说是因为我看到在MySQL中实现了太多的队列因为某种原因而失败。一个常见的原因是插入/检查/删除项目的开销可能与执行任务一样昂贵。那么为什么要加倍?而且,显然,排队造成了额外的死锁。
根据您提供的信息,系统应该能够每5分钟处理1500-3000。那应该处理&#34; 100&#34;到&#34;成千上万&#34;。
您的排队机制似乎过于复杂,因为它涉及JOIN
和其他不仅仅是1-in,1-out的事情。
假设你拒绝我的评论到目前为止,我将继续批评代码......
SELECT ... FOR UPDATE
两个SELECTs
可能都需要。
SELECT
旁边的DELETE
可能会与DELETE
合并为多表DELETE
。或者可以从事务中提取它以及相关代码。 (更快的交易不太可能陷入僵局。)
您正在COMMITs
之后检查错误(死锁等),是吗?那是加莱拉受到打击的时候。
使用IN(...)
时,对元素进行排序。底层代码可能按照IN
元素的顺序锁定行。此可以将死锁转换为最多innodb_lock_wait_timeout
秒的延迟。 (这种延迟不像“僵局”那么糟糕。)
当它发生死锁时你重复交易,对吗? (这是处理死锁的简单方法。)
编辑(IN)
如果一个线程正在执行UPDATE ... WHERE id IN (11,22)
而另一个线程正在执行UPDATE ... WHERE id IN (22,11)
,并且每个线程都锁定了一行,那么尝试将另一行锁定是一个死锁 - 并且必须{{} 1}}。相反,如果两者都表示(11,22),那么(最坏的情况下)就必须等待(但不会陷入僵局)。我假设,没有证据,InnoDB代码不足以避免这种ROLLBACK
死锁 - 通过对数字进行排序,通过原子锁定等等。 (而且我认为切割器=速度较慢,因此不值得做这么罕见的事情。)