1

我有一个基本上是先进先出队列的数据库表。行被系统的其他部分简单地插入到表中而被遗忘。每 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 ;
4

1 回答 1

0

我的口头禅:“不要排队,只要去做”。我这样说是因为我看到太多在 MySQL 中实现的队列由于某种原因而失败。一个常见的原因是插入/检查/删除项目的开销可能与“仅执行任务”一样昂贵。那为什么要双倍的成本呢?而且,显然,排队导致了额外的死锁。

根据您提供的信息,系统应该能够每 5 分钟处理 1500-3000 个。那应该处理“100”到“数千”。

您的排队机制似乎过于复杂,因为它涉及 aJOIN和其他不仅仅是 1 进 1 出的事情。

假设您到目前为止拒绝我的评论,我将继续批评代码......

SELECT ... FOR UPDATE

两者都可能需要SELECTs

SELECT旁边的可能DELETE会与DELETE作为多表合并DELETE。或者有可能将其连同相关代码从事务中提取出来。(更快的事务不太可能死锁。)

您正在检查 之后的错误(死锁等)COMMITs,是吗?那是Galera受到打击的时候。

使用 时IN(...),对元素进行排序。IN底层代码可能是按元素的顺序锁定行。这可能会将死锁变成长达innodb_lock_wait_timeout几秒钟的延迟。(这样的延迟并不像死锁那么“糟糕”。)

当事务陷入死锁时,您重复事务,对吗?(这是处理死锁的简单方法。)

编辑 (IN)

如果一个线程在做UPDATE ... WHERE id IN (11,22)而另一个线程在做UPDATE ... WHERE id IN (22,11),并且每个线程都锁定了一行,那么试图锁定另一行就是死锁——而且必须这样做ROLLBACK。相反,如果两者都说(11,22),那么(在最坏的情况下)一个人将不得不等待(但不会陷入僵局)。在没有证据的情况下,我假设 InnoDB 代码不足以以某种方式避免这种IN死锁——通过对数字进行排序、原子锁定或其他方式。(而且我认为 cleaver=slower,因此不值得为这种罕见的事情做。)

于 2015-07-02T05:20:10.273 回答