如何使用Django进行“批量更新”?

243

我想用Django更新一个表 - 类似于在原始SQL中执行以下操作:

update tbl_name set name = 'foo' where name = 'bar'

我的第一个结果是这样的 - 但那很讨厌,不是吗?

list = ModelClass.objects.filter(name = 'bar')
for obj in list:
    obj.name = 'foo'
    obj.save()

有更优雅的方法吗?


1
你可能正在寻找批量插入。请查看https://dev59.com/4m855IYBdhLWcg3whEtL - Pramod
我不喜欢插入新数据 - 只更新现有的。 - Thomas Schwärzl
3
也许可以借助select_for_update方法实现?详见https://docs.djangoproject.com/en/dev/ref/models/querysets/#django.db.models.query.QuerySet.select_for_update - Jure C.
“ModelClass”方法的好处是什么?然后将其作为输入提供给Django:https://dev59.com/questions/tGQn5IYBdhLWcg3wamjO - Ciro Santilli OurBigBook.com
8个回答

357

更新:

Django 2.2版本现在有一个bulk_update方法。

旧答案:

请参考以下django文档部分。

一次性更新多个对象

简单来说,你应该能够使用:

ModelClass.objects.filter(name='bar').update(name="foo")

你也可以使用F对象来执行一些操作,例如递增行:

from django.db.models import F
Entry.objects.all().update(n_pingbacks=F('n_pingbacks') + 1)

请参阅文档

但是,请注意:

  • 这不会使用ModelClass.save方法(因此,如果其中有某些逻辑,则不会触发它)。
  • 没有Django信号将被发送。
  • 您无法对切片的QuerySet执行.update()操作,必须在原始QuerySet上进行,因此您需要依赖于.filter().exclude()方法。

43
还需要注意的是,如果不使用save()方法,具有auto_now=True选项(也称为“修改时间”列)的DateTimeField字段将无法更新。 - Arthur
8
如果我有不同的数据需要更新不同的id,那么ModelClass.objects.filter(name='bar').update(name="foo")这个方法就不能实现批量更新的目的。那么,如果不想使用循环,该怎么做呢? - Shashank
@shihon,我不确定我是否理解你的意思,但我在答案中添加了示例。 - jb.
@Shashank 你解决了你的问题吗?我也遇到了同样的情况。 - Sourav Prem
1
F对象不能用于在.update方法中引用不同的模型...例如,您不能使用Entry.objects.all().update(title=F('blog__title'))。文档中有一个小小的提及。 如果您想从另一个模型中提取数据来更新条目,您将不得不运行一个for循环。 - sean.hudson
不要忘记使用 bulk_updatebatch_size 参数。 - Jill-Jênn Vie

33

考虑使用在GitHub上找到的django-bulk-update

安装: pip install django-bulk-update

实现: (代码直接取自项目的ReadMe文件)

from bulk_update.helper import bulk_update

random_names = ['Walter', 'The Dude', 'Donny', 'Jesus']
people = Person.objects.all()

for person in people:
    r = random.randrange(4)
    person.name = random_names[r]

bulk_update(people)  # updates all columns using the default db

更新:正如Marc在评论中指出的那样,这不适合一次更新数千行。虽然它适用于更新10到100个较小的批次。适合您的批处理大小取决于您的CPU和查询复杂性。这个工具更像是手推车而不是倾卸卡车。


21
我尝试过django-bulk-update,但我个人不建议使用它。它内部所做的是创建一个单一的SQL语句,类似于:UPDATE "table" SET "field" = CASE "id" WHEN %s THEN %s WHEN %s THEN %s [...] WHERE id in (%s, %s, [...]);。对于少量记录(不需要批量更新器)来说还可以,但对于10,000条记录来说,查询太复杂了,以至于Postgres花费更多的时间将CPU置于100%的状态来理解查询,而不是节省写入磁盘的时间。 - Marc Garcia
2
@MarcGarcia 很好的观点。我发现许多开发人员在不知道其影响的情况下使用外部库。 - Dejell
4
我不同意@MarcGarcia的观点,认为批量更新很有价值,在需要进行数千次更新时才真正需要使用。虽然像你提到的那样一次性更新10,000行并不可取,但是将其用于一次更新50行要比向数据库发送50个单独的更新请求更加高效。 - nu everest
6
我找到的最佳解决方案是:a)使用@transaction.atomic装饰器,通过使用单个事务来提高性能;或者b)在临时表中进行批量插入,然后从临时表到原始表进行UPDATE操作。 - Marc Garcia
1
我知道这是一个旧帖子,但实际上CASE/WHERE并不是唯一的方法。对于PostgreSQL,还有其他方法,但它们是特定于数据库的,例如https://dev59.com/3mMk5IYBdhLWcg3w-Slm#18799497。 然而,我不确定这是否在ANSI SQL中可行。 - Ilian Iliev
显示剩余4条评论

33

Django 2.2版本现在有一个bulk_update方法(发布说明)。

https://docs.djangoproject.com/en/stable/ref/models/querysets/#bulk-update

示例:

# get a pk: record dictionary of existing records
updates = YourModel.objects.filter(...).in_bulk()
....
# do something with the updates dict
....
if hasattr(YourModel.objects, 'bulk_update') and updates:
    # Use the new method
    YourModel.objects.bulk_update(updates.values(), [list the fields to update], batch_size=100)
else:
    # The old & slow way
    with transaction.atomic():
        for obj in updates.values():
            obj.save(update_fields=[list the fields to update])

1
确实,它在2.2版本的发布说明中列出 - Benoit Blanchon

16

我在互联网上发现了有关上述问题的有用内容

https://www.sankalpjonna.com/learn-django/running-a-bulk-update-with-django

低效的方法

model_qs= ModelClass.objects.filter(name = 'bar')
for obj in model_qs:
    obj.name = 'foo'
    obj.save()

高效的方式

ModelClass.objects.filter(name = 'bar').update(name="foo") # for single value 'foo' or add loop

使用bulk_update

update_list = []
model_qs= ModelClass.objects.filter(name = 'bar')
for model_obj in model_qs:
    model_obj.name = "foo" # Or what ever the value is for simplicty im providing foo only
    update_list.append(model_obj)
    
ModelClass.objects.bulk_update(update_list,['name'])

使用原子事务

from django.db import transaction

with transaction.atomic():
    model_qs = ModelClass.objects.filter(name = 'bar')
    for obj in model_qs:
       ModelClass.objects.filter(name = 'bar').update(name="foo")

有人点赞吗?提前感谢:感谢您关注;)


3
你的bulk_update示例对我来说毫无意义 - 为什么要重新从数据库中获取model_obj?与你提到的文章一样 - 它的bulk_update示例根本不需要进行任何db选择,它只需要执行User.objects.bulk_update([User(id=k, score=v) for k, v in user_ids_dict.items()], ['score'])即可... - jerch

15

如果你想在一组行中设置相同的值,你可以使用update()方法结合任何查询条件来更新所有行:

some_list = ModelClass.objects.filter(some condition).values('id')
ModelClass.objects.filter(pk__in=some_list).update(foo=bar)

如果你想根据某些条件更新一组不同值的行集合,你可以批量更新这些值。比如说你有1000行数据需要将一列设置为X个值中的一个,那么你可以事先准备好批次,然后只运行X次更新查询(每次查询基本上与上面的第一个示例具有相同的形式),加上初始的SELECT查询。

如果每行需要一个独特值,那么避免每个更新操作至少需要一个查询是无法避免的。也许在后一种情况下,你需要考虑其他架构,比如CQRS/事件溯源,以获得更好的性能。


12

要更新相同的值,我们可以简单地使用以下方法

ModelClass.objects.filter(name = 'bar').update(name='foo')

更新不同的值

ob_list = ModelClass.objects.filter(name = 'bar')
obj_to_be_update = []
for obj in obj_list:
    obj.name = "Dear "+obj.name
    obj_to_be_update.append(obj)
ModelClass.objects.bulk_update(obj_to_be_update, ['name'], batch_size=1000)

它不会每次都触发保存信号,而是将所有需要更新的对象保存在列表中,并一次性触发更新信号。


你的第二种情况应该仍然可以使用update,类似这样:ModelClass.objects.filter(name='bar').update(name = '亲爱的 ' + F('name')) - jerch
1
是的,我同意,但我在这里提到循环是为了增加一些更多的用途,不仅仅是连接字符串,我们甚至可以做一些计算、从其他来源读取数据、在循环内部进行if-else等操作。 - kathir raja

1

IT返回表中更新的对象数量。

update_counts = ModelClass.objects.filter(name='bar').update(name="foo")

您可以参考此链接获取有关批量更新和创建的更多信息。 批量更新和创建

0
在Django 4.1中,可以使用QuerySet.bulk_create()来更新字段。当行插入失败唯一性约束时,将执行更新。

现在,QuerySet.bulk_create()支持在行插入失败唯一性约束时更新字段。这在MariaDB、MySQL、PostgreSQL和SQLite 3.24+上都受支持。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接