Django的。线程安全更新或创建。

时间:2012-04-03 09:02:45

标签: python django

我们知道,更新 - 是线程安全的操作。 这意味着,当你这样做时:

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

而不是:

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

您的应用程序是相对线程安全的,操作SomeModel.objects.filter(id=1).update(some_field=100)不会重写其他模型字段中的数据。

我的问题是..如果有办法

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

但是如果它不存在则创建对象?

6 个答案:

答案 0 :(得分: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})

答案 1 :(得分:2)

您希望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()方法。

答案 2 :(得分:0)

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

你可以这样使用:

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

如果您有多个线程,您的应用需要确定哪个模型实例是正确的。通常我所做的是从数据库刷新模型,进行更改,然后保存它,这样就不会有很长时间处于断开状态。

答案 3 :(得分:0)

django不可能做这样的upsert操作,有了更新。但是queryset update方法返回已过滤字段的数量,因此您可以这样做:

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代码的情况下使用它,但更新或插入操作不会是原子的。如果在表上创建锁,则可以确保不会在另外两个线程中创建两个对象。

答案 4 :(得分:0)

我认为如果你对原子操作有重要要求。你最好在数据库级别而不是Django ORM级别设计它。

Django ORM系统专注于方便而不是性能和安全。您有时必须优化自动生成的SQL。

大多数高效数据库中的“事务”都可以很好地提供数据库锁定和回滚。

在mashup(混合)系统中,或者说您的系统添加了一些第三方组件,如日志记录,统计信息。不同框架甚至语言的应用程序可以同时访问数据库,在这种情况下在Django中添加线程安全是不够的。

答案 5 :(得分:-3)

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