2015-10-18 55 views
0

如何根据当前值更新模型字段并避免竞争条件?更新任务可以写成:在没有竞争条件的条件下更新Django

if (self.x == y): 
    self.x = z 
    self.save() 
else: 
    raise Exception() 

然而,有一个竞争条件。我想出了以下解决方案:

from django.db import transaction 
with transaction.atomic(): 
    if (self.x == y): 
     self.x = z 
     self.save() 
    else: 
     raise Exception() 

但是这是安全的,有没有更好的办法?

回答

2

不,那么atomic()块将不会执行任何操作,因为在尝试运行事务之前,您已经从数据库中提取了值并将其存入self

如果你可以表达你的查询参数的条件就可以放心地在单个查询中使用update()做到这一点:

num_matched = MyModel.objects.filter(id=self.id, x=y).update(x=z) 
if num_matched != 1: 
    raise Exception() 

如果没有,你可以使用select_for_update()

with transaction.atomic(): 
    current = MyModel.objects.select_for_update().get(id=self.id) 
    if (current.x == y): 
     current.x = z 
     current.save() 
    else: 
     raise Exception() 

之间的区别这和你上面的代码是在这里你明确地告诉数据库在做比较之前要锁定哪一行。

+0

首先,我想知道为什么你需要使用'transaction.atomic',因为'select_for_update'应该锁定行。但是,它仅锁定在单个事务的范围内,并且每个查询都将是它自己的事务(如果使用自动提交),如[在此解释](http://stackoverflow.com/a/17149748/2184571)所述。因此,你需要'transaction.atomic'包装,对吧? –

+1

@jluttine:是的,确切的。从文档:“在自动提交模式下用'select_for_update()'评估查询集...是一个'TransactionManagementError'错误,因为在这种情况下行没有被锁定。” –

+0

实际上,有几个问题:首先,不是'transaction.atomic'不必要的,因为'get'后面的唯一事务是'save'并且没有其他事务?其次,如果引发异常,是否释放了'select_for_update'锁定?在这种情况下,不会生成事务,因此锁不会被释放,或者是否? –