如何在限制SUM的INSERT上防止Django中的竞争条件?

时间:2017-05-15 12:14:18

标签: python django postgresql

考虑两种模式:

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

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

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

有没有办法在Django中执行此操作,请记住多人可能正在访问Web服务器并同时创建Expense

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

我尝试使用select_for_update,但这并不妨碍INSERT发生,而且无论如何它似乎都无法用于聚合。

我首先考虑saveExpense添加到数据库,然后查询总费用并删除Expense,如果它超出预算但我需要该代码在事务之外,以便其他线程可以看到它,然后如果服务器停止处理中,则数据可能会处于无效状态。

1 个答案:

答案 0 :(得分: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']