2015-10-18 81 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'鎖定?在這種情況下,不會生成事務,因此鎖不會被釋放,或者是否? –