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

Posted

技术标签:

【中文标题】如何使用 Django 进行“批量更新”?【英文标题】:How to 'bulk update' with Django? 【发布时间】:2012-09-21 14:13:18 【问题描述】:

我想用 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()

有没有更优雅的方式?

【问题讨论】:

您可能正在寻找批量插入。看看***.com/questions/4294088/… 我不喜欢插入新数据 - 只是更新现有数据。 也许在 select_for_update 的帮助下? docs.djangoproject.com/en/dev/ref/models/querysets/… ModelClass 方法有什么不好的地方?然后以:***.com/questions/16853649/… 的形式提供给 Django 【参考方案1】:

更新:

Django 2.2 版本现在有一个bulk_update。

旧答案:

请参阅以下 django 文档部分

Updating multiple objects at once

总之你应该可以使用:

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)

请参阅documentation。

但是,请注意:

这不会使用ModelClass.save 方法(所以如果你有一些逻辑,它不会被触发)。 不会发出 django 信号。 您不能在切片的 QuerySet 上执行 .update(),它必须在原始 QuerySet 上,因此您需要依靠 .filter().exclude() 方法。

【讨论】:

另请注意,由于不使用save()DateTimeField 字段与auto_now=True(“修改”列)将不会更新。 但是ModelClass.objects.filter(name = 'bar').update(name="foo") 不满足批量更新的目的,如果我有不同的数据用于不同的 id,我怎么能不使用循环来做到这一点? @shihon 我不确定我是否正确,但我在答案中添加了示例。 @Shashank 你找到任何解决方案了吗?我也有同样的情况。 F 对象不能用于在 .update 方法中引用不同的模型...例如你不能使用Entry.objects.all().update(title=F('blog__title'))。文档对此有一个小小的提及。如果你想从另一个模型中提取数据来更新你的条目,你必须运行一个 for 循环【参考方案2】:

考虑使用找到的django-bulk-update here on GitHub。

安装:pip install django-bulk-update

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

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 在 cmets 中指出的那样,这不适合一次更新数千行。虽然它适用于 10 到 100 的小批量。适合您的批处理大小取决于您的 CPU 和查询复杂性。这个工具更像是手推车而不是自卸卡车。

【讨论】:

我尝试了 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 在 100% 理解查询时花费更多时间在 CPU 上,而不是节省写入磁盘的时间. @MarcGarcia 好点。我发现许多开发人员在不知道其影响的情况下使用外部库 @MarcGarcia 我不同意批量更新没有价值,只有在需要数千次更新时才真正需要。由于您提到的原因,不建议使用它一次执行 10,000 行,但使用它一次更新 50 行比使用 50 个单独的更新请求访问数据库要高效得多。 我找到的最佳解决方案是:a) 使用 @transaction.atomic 装饰器,它通过使用单个事务来提高性能,或者 b) 在临时表中进行批量插入,然后从临时表到原始表。 我知道这是一个旧线程,但实际上 CASE/WHERE 并不是唯一的方法。对于 PostgreSQL,还有其他方法,但它们是特定于数据库的,例如***.com/a/18799497 但是我不确定这在 ANSI SQL 中是否可行【参考方案3】:

Django 2.2 版本现在有一个bulk_update 方法(release notes)。

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])

【讨论】:

确实,it is listed in the release notes for 2.2【参考方案4】:

如果您想在一组行上设置相同的值,您可以使用 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/事件溯源。

【讨论】:

【参考方案5】:

这是我在互联网上找到的关于上述问题的有用内容

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 obj in model_qs:
    model_obj =ModelClass.object.get(id=obj.id)
    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")

任何赞成票?提前致谢:感谢您的关注;)

【讨论】:

【参考方案6】:

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

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

使用不同的值进行更新

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

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

【讨论】:

【参考方案7】:

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

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

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

【讨论】:

以上是关于如何使用 Django 进行“批量更新”?的主要内容,如果未能解决你的问题,请参考以下文章

使用分组在 Django 中进行条件批量更新

在 Django Rest Framework 中批量更新数据

如何在不在 python 中迭代它们的情况下明显地批量更新 django 模型的所有对象?

如何使用 node.js 在 mySQL 中进行批量更新

如何在 Firestore 中进行批量更新

如何使用 Java 对 MongoDB 中的文档进行批量更新?