# Django 2.2.9.dev20191202104323 文档

# 数据库连接优化

Django 的数据库层提供了很多方式，帮助开发者充分使用它们的数据库。本文收集连接至关联文档，并添加了很多技巧，这些技巧被组织在多个标题下，这些标题概述了优化数据库时要采取的步骤。

## 先介绍一下

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

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

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

## 使用标准 DB 优化技巧

……包括：

- Indexes。 这是第一优先级，在您通过分析应该添加哪些索引*之后*确定。 使用：attr:Meta.indexes <django.db.models.Options.indexes>`或：attr:`Field.db_index <django.db.models.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</topics/cache>。

## 理解 `QuerySet`

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

### 理解 `QuerySet` 的执行过程

要避免执行过程中的问题，一定要理解：

- :ref:QuertSets 是惰性的 <querysets-are-lazy>`。
- 当 它们被计算时。
- 不过 数据保存在内存中。

### 理解缓存属性

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

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

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

    >>> entry = Entry.objects.get(id=1)
    >>> entry.authors.all()   # query performed
    >>> 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 在数据库中过滤。
- 使用 `F expressions` 来根据同一模型的其他字段进行过滤。
- 利用 注解在数据库中执行聚合。

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

### 使用 `RawSQL`

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

### 使用原生 SQL

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

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

当使用 `get()` 来检索单一对象时，使用 `unique` 或 `db_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")

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

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

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

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

### 使用 `QuerySet.select_related()` 和 `prefetch_related()`

深入理解 `select_related()` 和 `prefetch_related()` ，并使用它们：

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

## 不要检索你不需要的东西

### 使用 `QuerySet.values()` 和 `values_list()`

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

### 使用 `QuerySet.defer()` 和 `only()`

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

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

### 使用 `QuerySet.count()`

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

### 使用 `QuerySet.exists()`

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

但是：

### 请不要过度使用 `count()` 和 `exists()`

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

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

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

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

### 使用 `QuerySet.update()` 和 `delete()`

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

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

### 直接使用外键值

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

    entry.blog_id

替换成：

    entry.blog.id

### 如无需要，不要排序结果

排序是耗时的；对每个字段的排序是数据库必须执行的操作。如果模型有一个默认排序（ `Meta.ordering` ）并且不需要它，那么可以通过调用没有参数的 `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` ），因此要确保它适用于你的情况。

### 批量更新

New in Django 2.2.

当更新对象时，尽可能使用 `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.save()
    entries[1].headline = 'This is no longer a test'
    entries.save()

注意此方法有一些 `注意事项` ，因此确保它适合你的案例。

### 批量插入

当插入对象到 `ManyToManyFields` 时，使用带有多个对象的 `add()` 来减少 SQL 查询的数量。举例：

    my_band.members.add(me, my_friend)

要优于：

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

其中 `Bands` 和 `Artists` 有多对多关系。

当不同的对象对插入到 `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)

...其中 `Pizza` 和 `Topping` 是多对多关系。注意这里有一些注意事项（ `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)

其中 `Bands` 和 `Artists` 有多对多关系。

当从 `ManyToManyFields` 里删除不同的对象对时，可以在带有多种 `through` 模型实例的 `Q` 表达式上使用 `delete()` 来减少 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)

其中 `Pizza` 和 `Topping` 有多对多关系。