在 Django 中聚合 save()s?

Posted

技术标签:

【中文标题】在 Django 中聚合 save()s?【英文标题】:Aggregating save()s in Django? 【发布时间】:2011-03-24 15:21:51 【问题描述】:

我正在使用带有 sqlite 后端的 Django,写入性能是个问题。我可能会在某个阶段毕业到一个“合适的”数据库,但目前我被 sqlite 困住了。我认为我的写入性能问题可能与我正在创建大量行的事实有关,并且大概每次我save() 时都会锁定、解锁和同步磁盘上的数据库。

如何将大量 save() 调用聚合到单个数据库操作中?

【问题讨论】:

***.com/questions/1136106/… @Tomasz - 感谢您的链接,@commit_manually 装饰器为我提供了所需的性能改进。我已将我的问题标记为欺骗,因为我认为另一个问题很好地涵盖了它。 您实际上可以结束您的问题,而不是简单地投票支持结束,你知道的。 使用参数化查询也快得多。见***.com/questions/904796/… 【参考方案1】:

已编辑:commit_on_success 已被弃用并在 Django 1.8 中被删除。请改用transaction.atomic。请参阅 Fraser Harris 的answer。

实际上,这比您想象的要容易。你可以在 Django 中使用transactions。这些批处理数据库操作(特别是保存、插入和删除)成为一个操作。我发现最容易使用的是commit_on_success。本质上,您将数据库保存操作包装到一个函数中,然后使用 commit_on_success 装饰器。

from django.db.transaction import commit_on_success

@commit_on_success
def lot_of_saves(queryset):
    for item in queryset:
        modify_item(item)
        item.save()

这将有巨大的速度提升。如果任何项目失败,您还将获得回滚的好处。如果您有数百万个保存操作,那么您可能必须使用 commit_manuallytransaction.commit() 将它们分块提交,但我很少需要这样做。

希望对你有帮助,

【讨论】:

这简直太神奇了。我包装了一个创建大量记录的随机函数,在 SQLite 中它立即变得快了好几倍。 对于那些现在遇到此答案的人,commit_on_success 现在已弃用。取而代之的是atomic,这是新版本,会有同样的神奇效果! atomic 参考:docs.djangoproject.com/en/dev/topics/db/transactions/… 这绝对是白天制作的,批量插入快了大约 30 倍。真是神奇的效果!!【参考方案2】:

Django 1.6 的新功能是atomic, a simple API to control DB transactions。从文档中逐字复制:

atomic 可用作decorator:

from django.db import transaction

@transaction.atomic
def viewfunc(request):
    # This code executes inside a transaction.
    do_stuff()

作为context manager:

from django.db import transaction

def viewfunc(request):
    # This code executes in autocommit mode (Django's default).
    do_stuff()

    with transaction.atomic():
        # This code executes inside a transaction.
        do_more_stuff()

旧版 django.db.transaction 函数 autocommit()commit_on_success()commit_manually() 已被弃用,并将在 Django 1.8 中删除。

【讨论】:

【参考方案3】:

我认为这是您正在寻找的方法:https://docs.djangoproject.com/en/dev/ref/models/querysets/#bulk-create

从文档中复制的代码:

Entry.objects.bulk_create([
    Entry(headline='This is a test'),
    Entry(headline='This is only a test'),
])

在实践中,看起来像:

my_entries = list()
for i in range(100):
    my_entries.append(Entry(headline='Headline #'+str(i))

Entry.objects.bulk_create(my_entries)

根据文档,这将执行单个查询,而不管列表的大小(SQLite3 上最多 999 个项目),这对于 atomic 装饰器来说是不能说的。

有一个重要的区别。听起来,从 OP 的问题来看,他试图批量创建而不是批量保存atomic 装饰器是保存最快的解决方案,但不是创建

【讨论】:

我的一项工作遵循类似的方向。没有主键的模型有办法解决吗?我正在使用字典创建模型对象。没有 pk 我得到异常“dict object has no attribute pk”。【参考方案4】:

“如何将大量的 save() 调用聚合到单个数据库操作中?”

你不需要。 Django 已经为你管理了一个缓存。你不能通过尝试保存来改进它的数据库缓存。

“写入性能问题可能与我创建大量行有关”

正确。

SQLite 很慢。它就是这样儿的。查询比大多数其他数据库更快。写入速度很慢。

考虑更严重的架构更改。您是否在网络事务期间加载行(即批量上传文件并从这些文件加载​​数据库)?

如果您在网络事务中进行批量加载,请停止。你需要做一些更聪明的事情。使用celery 或使用其他一些“批处理”工具在后台进行加载。

我们尝试将自己限制在网络事务中的文件验证,并在用户不等待他们的 html 页面时进行加载。

【讨论】:

虽然可以公平地指出,由于它使用磁盘的方式,sqlite 确实在写入方面确实很慢,但它不必这个慢:正如建议的那样作为评论员,我开始使用 @commit_manually 装饰器,发现确实有很大的改进。 SQLite3 由于其文件锁定机制而运行缓慢。锁定和解锁文件以进行写访问需要时间。因此,将所有保存都放在与transaction.atomic() 的事务中将解决您的问题,因为许多save() 调用发生在单个文件锁定/文件解锁周期中。

以上是关于在 Django 中聚合 save()s?的主要内容,如果未能解决你的问题,请参考以下文章

django中的信号

Django模板语言相关内容 Djan

python,django,向mysql更新数据时save()报错不能用

django的数据库操作-16

使用 Django 时,save() 函数在使用模型表单时不起作用

带有自动提交事务的 Django save() 行为