数据库访问优化

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

轮廓优先

作为一般的编程实践,这是不言而喻的。找出 what queries you are doing and what they are costing you . 使用 QuerySet.explain() 了解具体情况 QuerySet s由数据库执行。您也可以使用外部项目,例如 django-debug-toolbar, 或者直接监视数据库的工具。

请记住,根据您的需求,您可能正在优化速度或内存或两者。有时为一个优化会对另一个不利,但有时它们会互相帮助。另外,由数据库进程完成的工作可能与在Python进程中完成的工作量不同(对您来说)。这取决于您决定您的优先级是什么,平衡在哪里,并根据需要对所有这些进行分析,因为这将取决于您的应用程序和服务器。

对于接下来的所有内容,请记住在每次更改之后进行概要分析,以确保更改是一个好处,并且考虑到代码可读性的降低,这是一个足够大的好处。 All 在下面的建议中,有一个警告是,在您的情况下,一般原则可能不适用,甚至可能被推翻。

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

……包括:

  • 索引。这是第一要务, 之后 您已经根据分析确定了应该添加哪些索引。使用 Meta.indexesField.db_index 从Django添加这些。考虑将索引添加到经常使用 filter()exclude()order_by() 等,因为索引可能有助于加速查找。请注意,确定最佳索引是一个复杂的依赖于数据库的主题,这将取决于您的特定应用程序。维护索引的开销可能超过查询速度的任何提高。

  • 适当使用字段类型。

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

理解 QuerySet 的S

理解 QuerySets 对于使用简单的代码获得良好的性能至关重要。特别地:

理解 QuerySet 评价

为了避免性能问题,必须了解:

了解缓存属性

以及整个数据库的缓存 QuerySet 中,存在对ORM对象上的属性结果的缓存。通常,不可调用的属性将被缓存。例如,假设 example blog 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() 也许有帮助。

使用 explain()

QuerySet.explain() 提供有关数据库如何执行查询的详细信息,包括使用的索引和联接。这些详细信息可以帮助您找到可以更高效地重写的查询,或者识别可以添加以提高性能的索引。

数据库在数据库中而不是在python中工作吗

例如:

如果这些还不足以生成所需的SQL:

使用 RawSQL

一种不太便携但更强大的方法是 RawSQL 表达式,允许将某些SQL显式添加到查询中。如果这还不够强大:

使用原始SQL

自己写 custom SQL to retrieve data or populate models . 使用 django.db.connection.queries 为了找出 Django 在为你写什么,从那里开始。

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

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

因此,使用 example blog models

>>> 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() . 这些对于替换模板代码中的模型对象很有用——只要您提供的dict与模板中使用的dict具有相同的属性,就可以了。

使用 QuerySet.defer()only()

使用 defer()only() 如果有数据库列,您知道不需要(或在大多数情况下不需要)来避免加载它们。注意,如果你 do 使用它们,ORM必须将它们放到一个单独的查询中,如果不恰当地使用它,这将是一个错误的模拟。

不要在没有分析的情况下过于激进地延迟字段,因为数据库必须从磁盘读取结果中的单个行的大多数非文本、非`VARCHAR``数据,即使它最终只使用了几列。这个 defer()only() 当您可以避免加载大量文本数据,或者对于可能需要进行大量处理才能转换回Python的字段时,方法是最有用的。一如既往,首先分析,然后优化。

使用 QuerySet.contains(obj)

如果你只想知道 obj 在查询集中,而不是 if obj in queryset

使用 QuerySet.count()

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

使用 QuerySet.exists()

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

但是:

不要过度使用 contains()count() ,以及 exists()

如果您需要来自QuerySet的其他数据,请立即对其求值。

例如,假设一个 Group 与具有多对多关系的模型 User ,以下代码是最佳代码:

members = group.members.all()

if display_group_members:
    if members:
        if current_user in members:
            print("You and", len(members) - 1, "other users are members of this group.")
        else:
            print("There are", len(members), "members in this group.")

        for member in members:
            print(member.username)
    else:
        print("There are no members in this group.")

它是最佳的,因为:

  1. 由于QuerySet是惰性的,因此在以下情况下不会进行数据库查询 display_group_membersFalse

  2. 储存 group.members.all()members 变量允许重复使用其结果缓存。

  3. 这条线 if members: 成因 QuerySet.__bool__() 被调用,这会导致 group.members.all() 要在数据库上运行的查询。如果没有任何结果,它将返回 False ,否则 True

  4. 这条线 if current_user in members: 检查用户是否在结果缓存中,这样就不会发出其他数据库查询。

  5. 对.的使用 len(members) 打电话 QuerySet.__len__() ,重用结果缓存,因此同样不会发出任何数据库查询。

  6. 这个 for member 循环遍历结果缓存。

总而言之,此代码要么执行一个数据库查询,要么执行零个数据库查询。唯一经过深思熟虑的优化是使用 members 变量。vbl.使用 QuerySet.exists() 对于 ifQuerySet.contains() 对于 in ,或 QuerySet.count() 对于计数,每一个都会引起额外的查询。

使用 QuerySet.update()delete()

不要检索一批对象、设置一些值并单独保存它们,而是使用大容量的SQL更新语句,通过 QuerySet.update() . 同样,做 bulk deletes 在可能的情况下。

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

直接使用外键值

如果您只需要一个外键值,请使用已经存在于您所拥有的对象上的外键值,而不是获取整个相关对象并获取其主键。即:

entry.blog_id

而不是::

entry.blog.id

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

排序不自由;要排序的每个字段都是数据库必须执行的操作。如果模型具有默认顺序 (Meta.ordering )你不需要它,把它放在 QuerySet 通过调用 order_by() 没有参数。

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

使用批量方法

使用批量方法来减少SQL语句的数量。

批量创建

创建对象时,如果可能,请使用 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 ,因此请确保它适合您的用例。

批量更新

更新对象时,如果可能,请使用 bulk_update() 方法来减少SQL查询的数量。给定一个对象列表或查询集:

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

以下示例:

entries[0].headline = "This is not a test"
entries[1].headline = "This is no longer a test"
Entry.objects.bulk_update(entries, ["headline"])

…优于:

entries[0].headline = "This is not a test"
entries[0].save()
entries[1].headline = "This is no longer a test"
entries[1].save()

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

批量插入

将对象插入 ManyToManyFields 使用 add() 以减少多个SQL对象的数量。例如::

my_band.members.add(me, my_friend)

…优于:

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

…在哪里 BandsArtists 有多对多的关系。

将不同的对象对插入到 ManyToManyField 或者当习俗 through 表已定义,使用 bulk_create() 方法来减少SQL查询的数量。例如::

PizzaToppingRelationship = Pizza.toppings.through
PizzaToppingRelationship.objects.bulk_create(
    [
        PizzaToppingRelationship(pizza=my_pizza, topping=pepperoni),
        PizzaToppingRelationship(pizza=your_pizza, topping=pepperoni),
        PizzaToppingRelationship(pizza=your_pizza, topping=mushroom),
    ],
    ignore_conflicts=True,
)

…优于:

my_pizza.toppings.add(pepperoni)
your_pizza.toppings.add(pepperoni, mushroom)

…在哪里 PizzaTopping 有多对多的关系。请注意,有许多 caveats to this method ,因此请确保它适合您的用例。

批量移除

从中移除对象时 ManyToManyFields 使用 remove() 以减少多个SQL对象的数量。例如::

my_band.members.remove(me, my_friend)

…优于:

my_band.members.remove(me)
my_band.members.remove(my_friend)

…在哪里 BandsArtists 有多对多的关系。

从中移除不同的对象对时 ManyToManyFields 使用 delete() 在一 Q 带多个 through 模型实例以减少SQL查询的数量。例如::

from django.db.models import Q

PizzaToppingRelationship = Pizza.toppings.through
PizzaToppingRelationship.objects.filter(
    Q(pizza=my_pizza, topping=pepperoni)
    | Q(pizza=your_pizza, topping=pepperoni)
    | Q(pizza=your_pizza, topping=mushroom)
).delete()

…优于:

my_pizza.toppings.remove(pepperoni)
your_pizza.toppings.remove(pepperoni, mushroom)

…在哪里 PizzaTopping 有多对多的关系。