2013-08-27 97 views
1

我有一個相當複雜的問題,導致我的數據庫中有重複的記錄。如何修復Django的get_or_create()導致數據重複的併發性

我在nginx 1.0.5上運行了uwsgi(4名工人)和Django 1.4.5。問題是,一些客戶端issueing重複請求相同的路徑,如所示下面nginx的日誌:

10.205.132.51 - - [26/Aug/2013:16:59:41 -0300] "GET /path/to/ HTTP/1.1" 499 0 "http://mydomain.com.br/path/" "Mozilla/5.0 (Windows NT 6.1; rv:21.0) Gecko/20100101 Firefox/21.0" 
10.205.132.51 - - [26/Aug/2013:16:59:41 -0300] "GET /path/to/ HTTP/1.1" 200 7372 "http://mydomain.com.br/path/" "Mozilla/5.0 (Windows NT 6.1; rv:21.0) Gecko/20100101 Firefox/21.0" 

這些請求被同時處理,而在像下面這種觀點的情況下,我進入比賽狀態其中get_or_create找到任何結果,都創建一個新的對象

with transaction.commit_on_success(): 
    f, created = cls.objects.get_or_create(
     key1=value1, 
     key2=value2, 
     defaults={...}) 

你問之前,沒有,這兩個鍵不是在數據庫unique_together

這兩個請求都返回來自Django的200狀態碼,但是nginx會丟棄一個,導致409狀態碼(衝突)。 transaction.commit_on_success()部分是一個減少的嘗試,但沒有解決問題。

我也嘗試了基於緩存的鎖,使用此功能:

@contextmanager 
def cache_exclusive(name, timeout=10): 
    """ found at http://coffeeonthekeyboard.com/simple-out-of-process-lock-with-python-and-memcached-2-985/ """ 
    key = 'cache_lock:%s' % name 
    lock = cache.add(key, True, timeout=timeout) # Fails if key already exists. 
    yield lock # Tell the inner block if it acquired the lock. 
    if lock: # Only clear the lock if we had it. 
     cache.delete(key) 

和獨特的名字與這個用法:

with cache_exclusive('key1 and key2') as granted: 
     if not granted: 
      return 
     # do the get_or_create stuff... 

但也沒打釘。您對如何處理這些重複請求有任何建議嗎?

+2

您正在使用什麼數據庫? – pvilas

+0

忘了提及。它是InnoDB引擎的mysql。 –

+0

也許你還想分享你正在努力實現的目標,因爲在數據庫級別解決問題可能會有另一種解決方案。 –

回答

2

此方法是原子假設正確的使用,正確的數據庫的配置,和底層數據庫的正確的行爲。但是,如果在數據庫級別對get_or_create調用中使用的kwargs沒有執行唯一性(請參見unique或unique_together),則此方法容易出現競爭條件,從而可能導致具有相同參數的多行同時插入。

如果您使用的是MySQL,請確保使用READ COMMITTED隔離級別而不是REPEATABLE READ(默認值),否則您可能會看到get_or_create會引發IntegrityError的情況,但該對象不會出現在隨後的get ()調用。

最後,關於在Django視圖中使用get_or_create()的一句話:請確保僅在POST請求中使用get_or_create(),除非您有充分的理由不要求GET請求不應該對數據產生任何影響;每當請求頁面時都使用POST作爲數據的副作用。有關更多信息,請參閱HTTP規範中的安全方法。

https://docs.djangoproject.com/en/dev/ref/models/querysets/#get-or-create

0
@transaction.commit_on_success 
def my_get_or_create(...): 
    try: 
     obj = MyObj.objects.create(...) 
    except IntegrityError: 
     transaction.commit() 
     obj = MyObj.objects.get(...) 
    return obj 

as seen here