【问题标题】:How to prevent race condition in Django on INSERT with limiting SUM?如何在 Django 中通过限制 SUM 在 INSERT 上防止竞争条件?
【发布时间】:2017-05-15 12:14:18
【问题描述】:

考虑两个模型:

  • 项目
    • id:AutoField 主键
    • 预算:A PositiveIntegerField
    • 一些不相关的字段
  • 费用
    • id:AutoField 主键
    • 金额:A PositiveIntegerField
    • 项目:A ForeignKeyProject
    • 一些不相关的字段

对于每个项目,我希望确保他们的费用总和始终小于或等于预算。

在 SQL 术语中:SELECT SUM(amount) FROM expense WHERE project_id = ?; 应始终小于或等于 SELECT budget FROM project WHERE id = ?;

在 Django 中有什么方法可以做到这一点,记住多人可能同时访问 Web 服务器并创建 Expenses?

我使用 postgresql 作为我的数据库后端。

我尝试过使用select_for_update,但这并不能阻止INSERTs 的发生,而且它似乎也不适用于聚合。

我正在考虑先将saveExpense 写入数据库,然后查询总成本并删除Expense(如果超出预算),但我需要将该代码放在事务之外,以便其他线程可以看到它,然后如果服务器在处理过程中停止,数据可能会处于无效状态。

【问题讨论】:

  • 您对select_for_update 做了什么尝试?尝试选择 project 而不是费用。
  • @Alasdair 那...比我预期的要简单得多。我不敢相信我没有想到这一点。无论如何,请您将其添加为答案,以便我接受。
  • 很高兴它有帮助。如果您使用一些示例代码添加答案,那么将来对其他用户可能会比我只是复制我的评论更有用。

标签: python django postgresql


【解决方案1】:

感谢@Alasdair 为我指明了正确的方向。

填写inst(新Expense)的字段后,执行:

with transaction.atomic():
    project = models.Project.objects.select_for_update().get(
        pk=project_id)
    cost = project.total_cost()
    budget = project.budget

    if cost + inst.cost > budget:
        raise forms.ValidationError(_('Over-budget'))

    self._inst.save()

请注意,我已将 total_cost 定义为 Project 上的方法:

class Project:
    def total_cost(self):
        return self.expense_set.all().aggregate(
            t=Sum(F('cost')))['t']

【讨论】:

    猜你喜欢
    • 2017-10-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-23
    • 1970-01-01
    • 2016-03-21
    • 2016-12-10
    • 2013-02-14
    相关资源
    最近更新 更多