2012-04-03 110 views
11

我們知道,更新 - 是線程安全操作。 這意味着,當你這樣做:Django。線程安全更新或創建。

SomeModel.objects.filter(id=1).update(some_field=100) 

相反的:

sm = SomeModel.objects.get(id=1) 
sm.some_field=100 
sm.save() 

您的應用程序是relativly線程運行安全SomeModel.objects.filter(id=1).update(some_field=100)不會改寫在其他模型字段中的數據。

我的問題是..如果有什麼辦法可以做到

SomeModel.objects.filter(id=1).update(some_field=100) 

但如果創作不存在,它的對象嗎?

回答

5
from django.db import IntegrityError 

def update_or_create(model, filter_kwargs, update_kwargs) 
    if not model.objects.filter(**filter_kwargs).update(**update_kwargs): 
     kwargs = filter_kwargs.copy() 
     kwargs.update(update_kwargs) 
     try: 
      model.objects.create(**kwargs) 
     except IntegrityError: 
      if not model.objects.filter(**filter_kwargs).update(**update_kwargs): 
       raise # re-raise IntegrityError 

我想,在問題中提供的代碼不是很有說服力:誰想爲模型設置id? 讓我們假設我們需要這個,我們必須同時操作:

def thread1(): 
    update_or_create(SomeModel, {'some_unique_field':1}, {'some_field': 1}) 

def thread2(): 
    update_or_create(SomeModel, {'some_unique_field':1}, {'some_field': 2}) 

隨着update_or_create功能,取決於哪個線程至上,對象將被創建並沒有例外更新。這將是線程安全的,但顯然有什麼用處:取決於SomeModek.objects.get(some__unique_field=1).some_field競爭條件值可以是1或2

Django提供˚F對象,所以我們可以提升我們的代碼:

from django.db.models import F 

def thread1(): 
    update_or_create(SomeModel, 
        {'some_unique_field':1}, 
        {'some_field': F('some_field') + 1}) 

def thread2(): 
    update_or_create(SomeModel, 
        {'some_unique_field':1}, 
        {'some_field': F('some_field') + 2}) 
+0

如果另一個進程在兩行之間創建對象,create()調用將引發一個IntegrityError。你也不會在create()調用中設置id。 – GDorn 2013-10-14 20:12:38

+0

好的,你說得對,它應該關心IntegrityError。將編輯代碼。 – Nik 2013-10-16 14:35:41

+0

請記住,您上面發佈的內容已經在django的查詢集的開發版中:https://docs.djangoproject.com/en/dev/ref/models/querysets/#update-or-create – 2013-10-31 12:26:01

0

您可以使用Django的內置get_or_create,但它可以在模型本身上運行,而不是在查詢集上運行。

您可以使用這樣的:

me = SomeModel.objects.get_or_create(id=1) 
me.some_field = 100 
me.save() 

如果你有多個線程,您的應用程序將需要確定模型的實例是正確的。通常,我所做的是從數據庫中刷新模型,進行更改,然後保存它,以便在斷開連接的狀態下沒有很長時間。

+0

是的,我這樣做,但那不是線程安全的。它將生成如下查詢:「UPDATE m SET field_1 = old_value1,field_2 = old_value2,some_field = 100',而不是'UPDATE m SET some_field = 100'。 – 2012-04-03 09:16:52

+0

我明白你的意思了。沒有線程安全的方法來做到這一點。如果您使用多個線程,則應在保存之前從數據庫中獲取最新信息。 – Jordan 2012-04-03 09:19:51

+0

順便說一下,我上面發佈的代碼將是「線程安全的」,因爲只要您在每次想要進行更新之前都執行get_or_create,它就會從數據庫中刷新。 – Jordan 2012-04-03 09:24:13

0

這是不可能在Django做這樣的upsert操作,更新。但是過濾領域的queryset的更新方法的返回號碼,以便你可以這樣做:

from django.db import router, connections, transaction 

class MySuperManager(models.Manager): 
    def _lock_table(self, lock='ACCESS EXCLUSIVE'): 
     cursor = connections[router.db_for_write(self.model)] 
     cursor.execute(
      'LOCK TABLE %s IN %s MODE' % (self.model._meta.db_table, lock) 
     ) 

    def create_or_update(self, id, **update_fields): 
     with transaction.commit_on_success():    
      self.lock_table() 
      if not self.get_query_set().filter(id=id).update(**update_fields): 
       self.model(id=id, **update_fields).save() 

這個例子中,如果Postgres的,你可以使用它沒有SQL代碼,但更新或插入操作不會是原子。如果你在表上創建一個鎖,你將確保兩個對象不會在另外兩個線程中創建。

+0

它仍然會生成類似於'INSERT INTO VALUES '。這意味着,如果我們將在一個線程中更新'A.one_field',並在第二個線程中更新'A.second_field' - 我們將會遇到麻煩。最後更新程序將使用舊數據擦除所有更新的字段。 **表鎖 - 這裏反解。**它會定期引發異常,但不會解決問題的根源。 – 2013-06-21 16:04:24

1

你希望django的select_for_update()方法(以及支持行級鎖定的後端,如PostgreSQL)結合手動事務管理。

try: 
    with transaction.commit_on_success(): 
     SomeModel.objects.create(pk=1, some_field=100) 
except IntegrityError: #unique id already exists, so update instead 
    with transaction.commit_on_success(): 
     object = SomeModel.objects.select_for_update().get(pk=1) 
     object.some_field=100 
     object.save() 

請注意,如果其他進程刪除兩個查詢之間的對象,您將得到一個SomeModel.DoesNotExist異常。

Django 1.7及以上版本還具有原子操作支持和內置update_or_create()方法。

+0

''update_or_create''is only in Django> = 1.7 – chaim 2014-08-10 03:06:24

0

我認爲如果你對原子操作有嚴格的要求。你最好在數據庫級別而不是Django ORM級別上設計它。

Django ORM系統專注於便利性而不是性能和安全性。有時您必須優化自動生成的SQL。

最具生產力的數據庫中的「事務」可以很好地提供數據庫鎖定和回滾。

在mashup(混合)系統中,或者說您的系統添加了第三方零部件,如日誌記錄,統計信息。應用在不同的框架甚至語言中可能同時訪問數據庫,在這種情況下在Django中添加線程安全性是不夠的。

+0

再次。我只是不想讓django更新模型保存的所有字段。所有數據庫級別的解決方案都無法在此工因爲Django本身從模型實例中獲取**舊值**,並用它們更新模型,即使我們只在代碼中更改了一個字段**。 – 2013-12-27 16:40:57

+0

如果你不關心最終結果,我的意思是字段值。你可以使用一個任務隊列系統(比如celery),設置一個專門的worker來update_or_create記錄,對數據庫的所有操作都會按順序執行。 – 2013-12-28 06:53:55

+0

芹菜在這裏矯枉過正。 :)在django開發分支''update_or_create'已經,所以問題是不實際的。 – 2013-12-28 07:36:36

-3
SomeModel.objects.filter(id=1).update(set__some_field=100) 
+0

請解釋你的代碼!你的答案是當前被投票結束。 – 2015-09-01 12:44:26