2012-04-03 72 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