最近我发现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()
以仅更新那些已修改的字段吗?
(对我来说这似乎是一个严重的问题。或者我做错了什么?)
答案 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
以确保您的写入一致。即使你只保存"脏"字段,并使其成为默认值,这仍然会发生。