2015-05-19 77 views
4

我對Django相當陌生,而且我不確定如何管理併發。確保保存模型時的原子性

我正在構建預留設備的應用程序。 我有一個ManytomanyField的設備預訂模型。 當我創建新的預訂時,我必須確保所有設備都可用。 我的代碼的第一個版本不關心併發:

def validate_reservation(keys, begin, end): 

    eqpts = Equipment.objects.filter(pk__in=keys) 
    if all(eqpt.is_available(begin, end) for eqpt in eqpts): 
     res = Reservation(eqpts, begin, end) 
     res.save() 
     return True 
    else: 
     return False 

的is_available方法檢查是否存在已經是對給定日期的任何保留:

def is_available(self, begin, end): 
     return not self.reservations.filter(begin__lte=end, end__gte=begin).exists() 

的問題是,兩個用戶運行如果在第二次檢查設備的可用性後第一次保存他的預訂,則此代碼可能會產生衝突預訂。

我相信我能解決交易問題,所以這次我什麼,我想出了:

def validate_reservation(keys, begin, end): 

    with transaction.atomic(): 
     eqpts = Equipment.objects.select_for_update().filter(pk__in=keys) 
     if all(eqpt.is_available(begin, end) for eqpt in eqpts): 
      res = Reservation(eqpts, begin, end) 
      res.save() 
      return True 
     else: 
      return False 

這是否有預期的行爲? 如何知道交易是否失敗並通知用戶?

有沒有不同的更好的方法?

回答

3

是的,只要您在修改任何可能影響您的可用性檢查的內容之前一直使用select_for_update(),就應該可以工作。

此代碼不會干擾自身,因爲select_for_update()將在開始時獲取關鍵行上的行級鎖定。但假設您有另一種觀點,允許人們輸入預訂pk並更改開始和結束時間,或添加新設備。這可能會導致不一致,除非您在與該Reservation關聯的Equipment行上明確執行相同的select_for_update()

請注意,您的支票效率不高。每當你在一個循環中執行查詢時,你應該尋找替代方案。你的情況,你應該能夠找到一個單一的查詢衝突(即JOINs表):

has_conflict = Reservation.objects.filter(begin__lte=end, 
              end__gte=begin, 
              equipment__pk__in=keys).exists() 

(當然,你仍然有併發問題,所以你仍然需要獲得適當的鎖或使用不同的事務隔離級別。)

如何知道事務是否失敗並通知用戶?

事務不會失敗,它會一直阻塞直到鎖定被釋放。如果您希望交易失敗,您可以使用select_for_update(nowait=True)(在某些數據庫上)。

+0

感謝您的回答,並且更好的檢查! 我的鎖仍然適合您的支票,對不對?你能告訴我更多關於你提到的其他事務隔離級別嗎? – Atn

+0

@An:你沒有提到你的數據庫,但[PostgreSQL文檔](http://www.postgresql.org/docs/9.4/static/transaction-iso.html)提供了事務隔離級別的一個很好的描述。也就是說,我更喜歡你的排鎖想法,並且它仍然可以和上面的檢查一樣。 –