数据库访问优化

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

简介第一

作为一般的编程习惯,这是不言而喻的。 找出what queries you are doing and what they are costing you 您可能还想使用外部项目,如django-debug-toolbar,或直接监控数据库的工具。

请记住,根据您的要求,您可能正在针对速度或内存或两者进行优化。 有时候,优化一个对另一个是不利的,但是有时他们会互相帮助。 而且,由数据库进程完成的工作可能与您在Python进程中完成的工作量相同(对您而言)。 这取决于您的应用程序和服务器,取决于您的优先级,平衡必须位于何处,并根据需要对所有这些进行配置。

随着以下所有内容,请记住在每次更改之后进行配置,以确保更改是一种好处,并且由于代码可读性的下降,可以获得足够的好处。 All of the suggestions below come with the caveat that in your circumstances the general principle might not apply, or might even be reversed.

使用标准的数据库优化技术

…包含:

  • 索引 T0>。 This is a number one priority, after you have determined from profiling what indexes should be added. 使用Field.db_indexMeta.index_together从Django中添加这些。 考虑将索引添加到您经常使用filter()exclude()order_by()等查询的字段中。加快查找速度。 请注意,确定最佳索引是一个复杂的数据库相关主题,将取决于您的特定应用程序。 维护索引的开销可能超过查询速度的任何收益。
  • 适当使用字段类型。

我们会假设你已经做了上面的明显的事情。 本文档的其余部分重点介绍如何使用Django,以避免不必要的工作。 本文档也没有涉及适用于所有昂贵操作的其他优化技术,如general purpose caching

了解QuerySet s

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

了解QuerySet评估

为了避免性能问题,了解以下内容非常重要:

了解缓存的属性

除了整个QuerySet的高速缓存外,还有对ORM对象的属性结果的高速缓存。 通常,不可调用的属性将被缓存。 例如,假设example Weblog models

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

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

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

使用with模板标签

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

使用iterator()

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

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

例如:

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

使用RawSQL

一个不太可移植但功能更强大的方法是RawSQL表达式,它允许将一些SQL明确地添加到查询中。 如果还不够强大:

使用原始SQL

编写自己的custom SQL to retrieve data or populate models 使用django.db.connection.queries来找出Django为你写的东西,并从那里开始。

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

当使用get()检索单个对象时,有两个原因使用uniquedb_index 首先,由于底层的数据库索引,查询会更快。 另外,如果多个对象匹配查询,查询可能运行得更慢;在列上有一个唯一的约束保证这绝不会发生。

因此,使用example Weblog models

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

会比以下更快:

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

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

执行以下操作可能非常慢:

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

First of all, headline is not indexed, which will make the underlying database fetch slower.

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

如果你知道你需要的话一次检索所有内容

对于单个“集合”数据的不同部分,您将需要多次访问数据库,一般而言,要比在一个查询中检索所有部分效率更低。 如果你有一个在循环中执行的查询,这是特别重要的,因此当只需要一个查询时,最终可能会做很多的数据库查询。 所以:

不要检索你不需要的东西

使用QuerySet.values()values_list()

当你只需要一个dictlist值,并且不需要ORM模型对象时,适当地使用values() 这些可以用于替换模板代码中的模型对象 - 只要您提供的字符串具有与模板中使用的字符相同的属性,就没有问题。

使用QuerySet.defer()only()

如果有数据库列,您可以使用defer()only()来避免加载它们。 请注意,如果do使用它们,那么ORM将不得不去使用它们,并将它们放在单独的查询中,如果不恰当地使用它,就会使其变得悲观。

另外,请注意,在构建具有延迟字段的模型时,Django内部会有一些额外的开销。 不要在没有性能分析的情况下推迟字段,因为数据库必须从结果中的单个行读取大部分非文本非VARCHAR数据,即使结果只是使用几列。 当您可以避免加载大量文本数据或可能需要大量处理转换回的字段时,defer()only()蟒蛇。 一如既往,首先配置文件,然后优化。

使用QuerySet.count()

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

使用QuerySet.exists()

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

但:

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

如果您要从QuerySet中获取其他数据,只需对其进行评估即可。

例如,假设一个具有body属性和与User的多对多关系的Email模型,以下模板代码是最优的:

{% 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. Use of with means that we store user.emails.all in a variable for later use, allowing its cache to be re-used.
  3. The line {% if emails %} causes QuerySet.__bool__() to be called, which causes the user.emails.all() query to be run on the database, and at the least the first line to be turned into an ORM object. 如果没有结果,则返回False,否则返回True。
  4. The use of {{ emails|length }} calls QuerySet.__len__(), filling out the rest of the cache without doing another query.
  5. The for loop iterates over the already filled cache.

总的来说,这段代码可以执行一个或零个数据库查询。 唯一有意识的优化是使用with标签。 在任何时候使用QuerySet.exists()QuerySet.count()都会导致额外的查询。

使用QuerySet.update()delete()

通过QuerySet.update(),而不是检索对象的负载,设置一些值并单独保存它们,使用批量SQL UPDATE语句。 同样,如果可能,请bulk deletes

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

直接使用外键值

如果只需要一个外键值,则使用已有的外键值,而不是获取整个相关对象并取其主键。 即:

entry.blog_id

代替:

entry.blog.id

如果你不在乎,不要命令结果

订购不是免费的;每个字段都是数据库必须执行的操作。 If a model has a default ordering (Meta.ordering) and you don’t need it, remove it on a QuerySet by calling order_by() with no parameters.

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

批量插入

在可能的情况下创建对象时,请使用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具有多对多的关系。