数据库连接优化

    作为一般的编程实践,这是不言而喻的。找出:ref:what queries you are doing and what they are costing you 。用法:meth:.Queryset.explain`了解数据库如何执行特定的``Queryset`s。您可能还希望使用外部项目,,或直接监视数据库的工具。

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

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

    使用标准 DB 优化技巧

    ……包括:

    • Indexes。 这是第一优先级,在您通过分析应该添加哪些索引*之后*确定。 使用:attr:Meta.indexes `或:attr:`Field.db_index `从Django添加这些。 考虑将索引添加到您经常使用的字段:meth:`~django.db.models.query.QuerySet.filter(),:meth:~django.db.models.query.QuerySet.exclude(), :meth:~django.db.models.query.QuerySet.order_by()`等作为索引可能有助于加快查找速度。 请注意,确定最佳索引是一个复杂的数据库相关主题,这依赖于您的特定应用程序。 维护索引的开销可能超过查询速度的任何增益。

    • 合理使用字段类型。

    我们假设您已完成上述事宜。 本文档的其余部分重点介绍如何以正确方式使用Django。 本文档也没有涉及适用于所有昂贵操作的其他优化技术,例如:doc:general purpose caching。

    理解 QuerySets 是用简单代码获得高效率的关键。特别是在:

    要避免执行过程中的问题,一定要理解:

    • :ref:QuertSets 是惰性的 `。
    • 当 。

    理解缓存属性

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

    但一般来说,callable 属性每次都会触发 DB 查询:

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

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

    小心你自己的自定义属性 - 你需要在需要时实现缓存,例如使用:class:`~django.utils.functional.cached_property`装饰器。

    使用 with 模板标签

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

    使用 iterator()

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

    使用 explain()

    QuerySet.explain() 会有关于数据库如何执行查询的详细介绍,包括索引和联合(joins)的使用。这些细节可以帮助你如何更高效的重写查询,或者识别可以添加的所以来改进性能。

    在数据库中执行数据库操作,而不是在 Python 代码中

    例子:

    • 在最基础的层上,使用 filter 和 exclude 在数据库中过滤。
    • 使用 来根据同一模型的其他字段进行过滤。
    • 利用 注解在数据库中执行聚合

    若其不足以生成你需要的 SQL:

    使用 RawSQL

    最简单直接的方法是 表达式,它允许一些 SQL 显式的添加到查询中。如果这还不够强大:

    编写自定义的SQL来检索数据或填充模型。使用 django.db.connection.queries 来找出 Django 为你编写的内容并从那里开始。

    当使用 get() 来检索单一对象时,使用 或 db_index 的列有两个原因。首先,由于底层数据库索引,所以查询会更快。此外,如果多个对象与查找匹配,查询就会很慢。对列具有唯一约束保证不会发生这种情况。

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

    会比以下更快:

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

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

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

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

    首先,headline 没有被索引,这将使得底层数据库获取变慢。

    其次,查找不保证只返回一个对象。如果查询匹配多于一个对象,它将从数据库中检索并传递所有对象。如果数据库位于单独的服务器上,那这个损失将更复杂,网络开销和延迟也是一个因素。

    如果你明确需要它,那么立即检索所有内容。

    对于你需要的所有部分的单个数据集的不同部分,多次访问数据库比单次查询所有内容的效率低。如果有一个查找,它在循环中执行,这点就尤其重要,当只需要一个查询时,最终会执行许多数据库查询。因此:

    使用 QuerySet.select_related()prefetch_related()

    深入理解 和 prefetch_related() ,并使用它们:

    • 在 中使用。请注意管理器何时被使用;有时这很棘手,所以不要做出假设。
    • 在视图代码层或其他层中,可能在需要时使用 prefetch_related_objects()

    使用 QuerySet.values()values_list()

    当你只想得到字典或列表的值,并且不需要 ORM 模型对象时,可以适当使用 。这些对于替换模板代码中的模型对象非常有用——只要你提供的字典具有与模板中使用时相同的属性就行。

    使用 QuerySet.defer()only()

    如果你明确不需要这个数据库列(或在大部分情况里不需要),使用 defer() 和 来避免加载它们。注意如果你使用它们,ORM 将必须在单独的查询中获取它们,如果你不恰当的使用,会让事情变得糟糕。

    不要在没有分析的情况下过分使用延迟字段,因为数据库必须从磁盘中读取结果中单行的大部分非文本、非VARCHAR数据,即使它最终只使用的几列。当你不想加载许多文本数据或需要大量处理来转换回 Python的字段, defer()only() 方法最有用。总之,先分析,再优化。

    使用 QuerySet.count()

    ……如果你只想计数,不要使用 len(queryset)

    使用 QuerySet.exists()

    ……若你只想要确认是否有至少存在一项满足条件的结果,而不是 if queryset

    但是:

    如果你需要查询集中的其他数据,请立即对其进行评估。

    比如,假设 Email 模型有 body 属性和与 User 的多对多关系 ,下面的模板代码是最佳的:

    1. {% if display_inbox %}
    2. {% with emails=user.emails.all %}
    3. {% if emails %}
    4. <p>You have {{ emails|length }} email(s)</p>
    5. {% for email in emails %}
    6. <p>{{ email.body }}</p>
    7. {% endfor %}
    8. {% else %}
    9. <p>No messages today.</p>
    10. {% endif %}
    11. {% endwith %}
    12. {% endif %}

    这是最佳的,因为:

    1. 因为查询集是懒加载,如果 ‘display_inbox’ 是 False,就不会有数据库查询。
    2. 使用 with 意味着我们在一个变量中存储 user.emails.all 以供日后使用,允许重用缓存。
    3. {% if emails %} 会调用 QuerySet.__bool__() ,这会使得在数据库上执行 user.emails.all() 。如果没有结果,将返回 False,否则返回 True。
    4. {{ emails|length }} 会调用 QuerySet.__len__() ,填写剩余的缓存而不执行其他查询。
    5. 循环遍历已有的缓存。

    总之,这个代码会执行一条或零条数据库查询。唯一经过深思熟虑的优化是 with 标签。在任何时候使用 QuerySet.exists()QuerySet.count() 会导致额外的查询。

    使用 QuerySet.update()delete()

    如果要设置一些值并单独保存它们,而不是检索对象,那么可以通过 使用批量 SQL UPDATE 语句。类似地,尽可能使用批量删除( bulk deletes )。

    注意,尽管这些批量更新方法不会调用单独实例的 save()delete() 方法,这意味着你为这些方法添加的任何自定义行为都不会执行,包括来自正常数据库对象信号( )的任何内容。

    直接使用外键值

    如果只需要外键值,那么使用已有对象上的外键值,而不是获取所有相关对象并获取它的主键。比如:

    1. entry.blog_id

    替换成:

    如无需要,不要排序结果

    添加索引到你的数据库上可以帮助改进排序性能。

    使用批量方法

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

    批量创建

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

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

    要优于:

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

    注意这个方法有一些注意事项( ),因此要确保它适用于你的情况。

    批量更新

    当更新对象时,尽可能使用 bulk_update() 方法来减少 SQL 查询数。给定对象的列表或查询集:

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

    下面示例:

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

    要优于:

    1. entries[0].headline = 'This is not a test'
    2. entries[0].save()
    3. entries[1].headline = 'This is no longer a test'
    4. entries[1].save()

    注意此方法有一些 ,因此确保它适合你的案例。

    当插入对象到 ManyToManyFields 时,使用带有多个对象的 来减少 SQL 查询的数量。举例:

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

    要优于:

    其中 BandsArtists 有多对多关系。

    当不同的对象对插入到 ManyToManyField 或者自定义的 表被定义时,可以使用 bulk_create() 方法来减少 SQL 查询的数量。比如:

    1. PizzaToppingRelationship = Pizza.toppings.through
    2. PizzaToppingRelationship.objects.bulk_create([
    3. PizzaToppingRelationship(pizza=my_pizza, topping=pepperoni),
    4. PizzaToppingRelationship(pizza=your_pizza, topping=pepperoni),
    5. PizzaToppingRelationship(pizza=your_pizza, topping=mushroom),
    6. ], ignore_conflicts=True)

    要优于:

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

    …其中 PizzaTopping 是多对多关系。注意这里有一些注意事项( ),因此要确保它适用于你的案例。

    批量删除

    当从 ManyToManyFields 删除对象时,可以使用带有多个对象的 来减少 SQL 查询的数量。比如:

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

    要优于:

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

    其中 BandsArtists 有多对多关系。

    当从 ManyToManyFields 里删除不同的对象对时,可以在带有多种 模型实例的 Q 表达式上使用 来减少 SQL 查询的数量。比如:

    1. from django.db.models import Q
    2. PizzaToppingRelationship = Pizza.toppings.through
    3. PizzaToppingRelationship.objects.filter(
    4. Q(pizza=my_pizza, topping=pepperoni) |
    5. Q(pizza=your_pizza, topping=pepperoni) |
    6. Q(pizza=your_pizza, topping=mushroom)
    7. ).delete()

    要优于:

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

    其中 和 Topping 有多对多关系。