【问题标题】:Deadlock with concurrent Celery workers in DjangoDjango 中并发 Celery 工作人员的死锁
【发布时间】:2021-08-18 04:18:33
【问题描述】:

在我的 Django 应用程序中,我有几十个并发的 Celery 工作人员从各种模型中创建、更新和删除数据。我遇到了以下错误:

deadlock detected
DETAIL:  Process 3285070 waits for ShareLock on transaction 559341801; blocked by process 3285058.
Process 3285058 waits for ShareLock on transaction 559341803; blocked by process 3285070.
HINT:  See server log for query details.

这些是引发错误的代码块:

with transaction.atomic():
    for score in list_of_scores_to_update:
        score.save()
time_delta = timezone.now() - timezone.timedelta(minutes=2)
with transaction.atomic():
    Score.objects.select_for_update(of=('self'), skip_locked=True).filter(checked_date__lte=time_delta).delete()

如何通过在更新所有行之前获取所有行的写锁来防止这些死锁?

【问题讨论】:

    标签: django postgresql django-models django-rest-framework deadlock


    【解决方案1】:

    防止死锁的最好方法是

    • 保持交易简短

    • 让您的交易保持小规模(尽可能少地更改行)

    现在可能是您的要求使这变得困难。然后,您可以通过在所有事务中按特定顺序锁定行来避免死锁。例如,始终按主键顺序锁定行。

    最后,如果您没有遇到很多死锁,您就不必尝试避免它们。如果遇到死锁,所需要的只是重复事务。这可以正常工作,但如果死锁过多,则会导致性能问题。

    抱歉,您提供的数据很少,无法说更多。您可能必须查看死锁中涉及的事务发出的所有语句。

    【讨论】:

    • 如果发生死锁,有没有办法在没有人工干预的情况下重新运行 Celery 任务。
    • 这超出了我的认知;我不知道芹菜。
    【解决方案2】:

    所以在一个地方,你更新了Score,而在其他地方它们被删除了?

    我认为罪魁祸首是你迭代list_of_scores_to_update 的地方。您需要做的是预取批次/对查询集进行分页,然后打开事务以更新此批次,而不是整个查询集。首先选择没有锁的相关pks,然后在事务中更新它们的子集。见iterator

    至于删除操作,如果您的用例允许,您可以将pk 添加到另一个队列。然后另一个任务可以从这个队列中预取多个 id 并尝试删除。失败时重试。这是一个非常简单的解决方案,无需人工干预即可重试。

    总而言之,我确定list_of_scores_to_update 的锁太大了,所以试着把它变小。 根据第二个 sn-p,将删除与过滤分离,以便重试删除过程。

    【讨论】:

      猜你喜欢
      • 2021-09-08
      • 1970-01-01
      • 2015-06-23
      • 2021-08-15
      • 1970-01-01
      • 2014-09-25
      • 1970-01-01
      • 1970-01-01
      • 2013-02-18
      相关资源
      最近更新 更多