我们知道,更新 - 是线程安全的操作。 这意味着,当你这样做时:
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)
但是如果它不存在则创建对象?
答案 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)