Django的Model.save()修改了不需要的列

时间:2016-07-31 12:12:09

标签: python sql django

最近我发现Django ORM的Model.save()执行了一个SQL来更新' ALL'默认情况下,即使没有修改任何内容。 这真的让我感到担忧,因为我所做的任何更改都有可能被其他Model.save()进程重新设置为原始值。

例如,我有一个模型Order。并且有两个并发进程(P1,P2)正在处理它。首先,P1选择一行:

# P1
order = Order.objects.get(pk=10000)

然后,P2选择同一行并更新status列:(以下语句可以包含在事务中,甚至可以包含在SERIALIZABLE中,但这不能解决问题。)

# P2
order = Order.objects.get(pk=10000)
if order.status == UNPAID:
    order.status = PAID    # update the `status` column
    order.save()

之后,P1会更新其他一些琐碎的专栏:

# P1
order.xxx = xxx    # update some other trivial column
order.save()    # This would set the `status` back to UNPAID !!!

order.status将被设置回UNPAID,这不是我想要的。

我知道我可以在P1上使用save(update_fields=...)select_for_update()filter(...).update(...),SERIALIZABLE事务或显式锁定来防止此问题。 但问题是:在整个项目的所有Model.save()语句中使用它们是荒谬的。此外,即使我在我的项目代码中,还有其他一些代码(Django Admin,ModelForm ......)。

我应该重写Model.save()以仅更新那些已修改的字段吗?

(对我来说这似乎是一个严重的问题。或者我做错了什么?)

2 个答案:

答案 0 :(得分:0)

正如其他人在评论中所说,你有race condition的经典类型。有一些方法可以在Django中处理这个问题。您已经提到了其中一些,但根据我的经验,它们不能被视为安全。我强烈建议您观看名为Immutable Django的{​​{3}}并开始向这个方向挖掘。

基本上,您可以在模型中添加某种version列,并在每次保存时将其递增,并与现有列进行比较。如果新版本相同或更少,则已保存一个 - 您尝试保存不实际的条目。

答案 1 :(得分:0)

您只需要一列就可以发生类似的竞争条件:

# Process 1
account = Account.objects.get(pk=1)
account.balance += 1000

# Process 2
account = Account.objects.get(pk=1)
account.balance -= 10
account.save()

# Process 1
account.save()

现在,流程2的余额扣除将丢失。您必须使用select_for_update以确保您的写入一致。即使你只保存"脏"字段,并使其成为默认值,这仍然会发生。