Skip to main content

数据库访问优化

Django的数据库层提供了各种方法来帮助开发人员充分利用他们的数据库。本文档汇集了相关文档的链接,并添加了一些标题下的各种提示,概述了在尝试优化数据库使用情况时要采取的步骤。

个人资料第一

作为一般的程序设计实践,这是不言而喻的。找出 你正在做什么查询和他们正在花费你。您可能还需要使用外部项目(如 django-debug-toolbar)或直接监视数据库的工具。

请记住,您可能会根据您的要求对速度或内存或两者进行优化。有时优化一个会对另一个有害,但有时他们会互相帮助。此外,由数据库进程完成的工作可能与在Python进程中完成的工作量相同(对您)。由你决定你的优先级是什么,天平必须在哪里,并根据需要配置所有这些,这将取决于您的应用程序和服务器。

随后的一切,记住在每次更改后配置文件,以确保更改是一个好处,并有一个足够大的好处,因为您的代码的可读性降低。 所有 的以下建议附带警告,在您的情况下,一般原则可能不适用,或甚至可能被扭转。

使用标准数据库优化技术

...包含:

  • Indexes。这是一个第一优先级,after 你从分析确定应该添加哪些索引。使用 Field.db_indexMeta.index_together 从Django添加这些。考虑将索引添加到您经常使用 filter()exclude()order_by() 等查询的字段,因为索引可能有助于加快查找速度。请注意,确定最佳索引是一个依赖于特定应用程序的复杂数据库相关主题。维护索引的开销可能超过查询速度的任何增益。

  • 适当使用字段类型。

我们将假设你做了上面明显的事情。本文档的其余部分重点介绍如何使用Django,这样您就不必进行不必要的工作。本文也没有涉及适用于所有昂贵操作的其他优化技术,例如 通用缓存

了解 QuerySet

了解 QuerySets 对于使用简单代码获得良好性能至关重要。尤其是:

了解 QuerySet 评估

为了避免性能问题,重要的是要了解:

了解缓存的属性

除了缓存整个 QuerySet 之外,还有对ORM对象的属性结果的缓存。一般来说,不可调用的属性将被缓存。例如,假设 示例Weblog模型:

>>> entry = Entry.objects.get(id=1)
>>> entry.blog   # Blog object is retrieved at this point
>>> entry.blog   # cached version, no DB access

但一般来说,可调用属性每次都会导致数据库查找:

>>> entry = Entry.objects.get(id=1)
>>> entry.authors.all()   # query performed
>>> entry.authors.all()   # query performed again

在阅读模板代码时要小心 - 模板系统不允许使用括号,但会自动调用callables,隐藏上述区别。

小心你自己的自定义属性 - 它是由你来实现缓存需要时,例如使用 cached_property 装饰器。

使用 with 模板标记

要使用 QuerySet 的缓存行为,您可能需要使用 with 模板标记。

使用 iterator()

当你有很多对象时,QuerySet 的缓存行为可能会导致大量的内存被使用。在这种情况下,iterator() 可能会有帮助。

数据库在数据库中工作,而不是在Python中工作

例如:

如果这些不足以生成SQL,您需要:

使用 RawSQL

RawSQL 表达式不太方便,但更强大的方法,它允许一些SQL被显式地添加到查询。如果仍然不够强大:

使用原始SQL

写你自己的 自定义SQL以检索数据或填充模型。使用 django.db.connection.queries 找出Django为你写的东西,并从那里开始。

使用唯一的索引列检索单个对象

当使用 get() 检索单个对象时,使用具有 uniquedb_index 的列有两个原因。首先,查询将更快,因为基础数据库索引。此外,如果多个对象匹配查找,查询可能运行慢得多;在列上有一个唯一的约束保证这永远不会发生。

所以使用 示例Weblog模型:

>>> entry = Entry.objects.get(id=10)

将比以下更快:

>>> entry = Entry.objects.get(headline="News Item Title")

因为 id 由数据库索引并且保证是唯一的。

执行以下操作可能相当缓慢:

>>> entry = Entry.objects.get(headline__startswith="News")

首先,headline 没有索引,这将使基础数据库提取速度变慢。

其次,查找并不保证只返回一个对象。如果查询匹配多个对象,它将从数据库检索和传输所有对象。如果返回数百或数千条记录,这种惩罚可能是重大的。如果数据库位于单独的服务器上,那么惩罚将更加复杂,其中网络开销和延迟也是一个因素。

如果你知道你会需要它,立即检索一切

对于单个“集合”数据的不同部分,您需要所有部分的数据库多次击中数据库通常比在一个查询中检索所有部分效率低。这是特别重要的,如果你有一个循环中执行的查询,因此可能会结束许多数据库查询,当只需要一个。所以:

不要检索你不需要的东西

使用 QuerySet.values()values_list()

当您只需要 dictlist 的值,并且不需要ORM模型对象时,请适当使用 values()。这些可以用于替换模板代码中的模型对象 - 只要您提供的对象具有与模板中使用的属性相同的属性即可。

使用 QuerySet.defer()only()

使用 defer()only(),如果有数据库列,你知道你不需要(或在大多数情况下不需要),以避免加载它们。注意,如果 do 使用它们,ORM将不得不在单独的查询中获取它们,如果您不适当地使用它,这将是一个pessimization。

此外,请注意,在构造具有延迟字段的模型时,Django内部会产生一些(小的额外)开销。不要在没有分析的情况下延迟字段,因为数据库必须从结果中的单个行读取磁盘中的大多数非文本非VARCHAR数据,即使它最终只使用几列。 defer()only() 方法在您可以避免加载大量文本数据或可能需要大量处理以转换回Python的字段时最有用。一如既往,首先剖析,然后优化。

使用 QuerySet.count()

...如果你只想要计数,而不是做 len(queryset)

使用 QuerySet.exists()

...如果你只想知道是否至少有一个结果存在,而不是 if queryset

但:

不要过度使用 count()exists()

如果您将需要QuerySet中的其他数据,只需评估它。

例如,假设具有 body 属性和与用户的多对多关系的电子邮件模型,以下模板代码是最佳的:

{% if display_inbox %}
  {% with emails=user.emails.all %}
    {% if emails %}
      <p>You have {{ emails|length }} email(s)</p>
      {% for email in emails %}
        <p>{{ email.body }}</p>
      {% endfor %}
    {% else %}
      <p>No messages today.</p>
    {% endif %}
  {% endwith %}
{% endif %}

它是最佳的,因为:

  1. 由于QuerySets是延迟的,如果’display_inbox’为False,则不会执行数据库查询。

  2. 使用 with 意味着我们将 user.emails.all 存储在变量中供以后使用,允许重新使用其缓存。

  3. 线 {% if emails %} 导致 QuerySet.__bool__() 被调用,这使得 user.emails.all() 查询在数据库上运行,并且至少第一行被转换为ORM对象。如果没有任何结果,它将返回False,否则返回True。

  4. 使用 {{ emails|length }} 调用 QuerySet.__len__(),填充其余的缓存,而不进行另一个查询。

  5. for 循环遍历已经填充的高速缓存。

总的来说,此代码执行一个或零个数据库查询。唯一有意的优化是使用 with 标签。在任何点使用 QuerySet.exists()QuerySet.count() 将导致额外的查询。

使用 QuerySet.update()delete()

不是检索一个对象的加载,设置一些值,并将它们保存为单独的,使用批量SQL UPDATE语句,通过 QuerySet.update()。同样,尽可能做 批量删除

但请注意,这些批量更新方法不能调用单个实例的 save()delete() 方法,这意味着您为这些方法添加的任何自定义行为将不会被执行,包括从正常数据库对象 信号 驱动的任何行为。

直接使用外键值

如果你只需要一个外键值,使用已经在你拥有的对象上的外键值,而不是获取整个相关对象和获取它的主键。即do:

entry.blog_id

代替:

entry.blog.id

如果你不在乎,不要订购结果

订购不是免费的;每个字段的order by是数据库必须执行的操作。如果模型具有默认排序(Meta.ordering)且您不需要它,则通过调用没有参数的 order_by()QuerySet 上删除它。

向数据库添加索引可能有助于提高排序性能。

批量插入

在创建对象时,尽可能使用 bulk_create() 方法来减少SQL查询的数量。例如:

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

...更可取:

Entry.objects.create(headline='This is a test')
Entry.objects.create(headline='This is only a test')

请注意,有一些 caveats to this method,因此请确保它适合您的用例。

这也适用于 ManyToManyFields,这样做:

my_band.members.add(me, my_friend)

...更可取:

my_band.members.add(me)
my_band.members.add(my_friend)

...其中 BandsArtists 有多对多关系。