# Django 5.2.8 documentation

# 数据库访问优化

Django 的数据库层提供了各种方法来帮助开发者最大限度地利用数据库。本文档收集了相关文档的链接，并添加了各种提示，按照一些标题组织，概述了在尝试优化数据库使用时的步骤。

## 首先性能分析

作为一般的编程实践，这是不言而喻的。找出 你在做什么查询以及它们的成本。使用 `QuerySet.explain()` 来了解你的数据库如何执行特定的 `QuerySet`。你可能还想使用像 django-debug-toolbar 这样的外部项目，或者直接监控你的数据库的工具。

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

对于下面的所有内容，请记住在每次修改后都要进行性能分析，以确保修改有好处，而且是一个足够大的好处，因为你的代码的可读性降低了。以下所有 的建议都有一个警告，那就是在你自身情况下，一般的原则可能不适用，甚至可能会被反过来。

## 使用标准数据库优化技巧

……包括：

- Indexes. This is a number one priority, after you have determined from profiling what indexes should be added. Use `Meta.indexes` or `Field.db_index` to add these from Django. Consider adding indexes to fields that you frequently query using `filter()`, `exclude()`, `order_by()`, etc. as indexes may help to speed up lookups. Note that determining the best indexes is a complex database-dependent topic that will depend on your particular application. The overhead of maintaining an index may outweigh any gains in query speed.

- 合理使用字段类型。

我们将假设你已经做了上面列出的事情。本文档的其余部分主要介绍如何使用 Django，使你不做不必要的工作。本文档也不涉及其他适用于所有昂贵操作的优化技术，比如 通用缓存。

## 理解 `QuerySet`

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

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

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

- QuertSets 是惰性的。

- 当 它们被计算时。

- 不过 数据保存在内存中。

### 理解缓存属性

除了整个 `QuerySet` 的缓存之外，还对 ORM 对象上的属性结果进行了缓存。通常，不可调用的属性会被缓存。例如，假设 示例博客模型：

    >>> 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()`

When you have a lot of objects, the caching behavior of the `QuerySet` can cause a large amount of memory to be used. In this case, `iterator()` may help.

### 使用 `explain()`

`QuerySet.explain()` 为你提供有关数据库如何执行查询的详细信息，包括使用的索引和连接（jion）。这些细节可能会帮助你找到可以更有效地重写的查询，或确定可以添加的索引以提高性能。

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

例子：

- 在最基本的层面上，使用 filter 和 exclude 在数据库中进行过滤。

- 使用 `F 表达式` 根据同一模型中的其他字段进行过滤。

- 利用 注解在数据库中执行聚合。

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

### 使用 `RawSQL`

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

### 使用原生 SQL

编写你自己的 自定义 SQL 来检索数据或填充模型。使用 `django.db.connection.query` 找出 Django 为你写的东西，然后从那里开始。

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

在使用 `get()` 来检索单个对象时，有两条理由可以使用带有属性 `unique` 或 `db_index` 的列。首先，查询将更快，因为底层数据库索引的存在加快了速度。此外，如果多个对象匹配查找条件，则查询可能会运行得非常慢；在该列上设置唯一约束可以保证这种情况永远不会发生。

因此，使用 示例博客模型：

    >>> 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()` ，并使用它们：

- 在 管理器和默认管理器 中使用。请注意管理器何时被使用；有时这很棘手，所以不要做出假设。

- 在视图代码层或其他层中，可能在需要时使用 `prefetch_related_objects()` 。

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

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

When you only want a `dict` or `list` of values, and don't need ORM model objects, make appropriate usage of `values()`. These can be useful for replacing model objects in template code - as long as the dicts you supply have the same attributes as those used in the template, you are fine.

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

Use `defer()` and `only()` if there are database columns you know that you won't need (or won't need in most cases) to avoid loading them. Note that if you do use them, the ORM will have to go and get them in a separate query, making this a pessimization if you use it inappropriately.

在不进行性能分析的情况下，不要过于激进地推迟字段，因为数据库必须从磁盘读取大多数非文本、非 `VARCHAR` 数据，即使最终只使用了几列。`defer()` 和 `only()` 方法在你可以避免加载大量文本数据或对需要大量处理才能转换回 Python 的字段时最有用。与往常一样，首先进行性能分析，然后进行优化。

### 使用 `QuerySet.contains(obj)`

...如果你只想找出 `obj` 是否在查询集中，而不是 `if obj in queryset`。

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

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

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

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

但是：

### 不要滥用 `contains()`, `count()`, 和 `exists()`。

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

例如，假设有一个 `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. 由于查询集是惰性加载的，如果 `display_group_members` 是 `False`，则不会执行任何数据库查询。

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` 变量。使用 `QuerySet.exists()` 来进行 `if`、使用 `QuerySet.contains()` 来进行 `in`，或者使用 `QuerySet.count()` 来进行计数都会导致额外的查询。

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

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

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

### 直接使用外键值

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

    entry.blog_id

替换成：

    entry.blog.id

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

Ordering is not free; each field to order by is an operation the database must perform. 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.

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

## 使用批量方法

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

### 批量创建

When creating objects, where possible, use the `bulk_create()` method to reduce the number of SQL queries. For example:

    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` ），因此要确保它适用于你的情况。

### 批量更新

When updating objects, where possible, use the `bulk_update()` method to reduce the number of SQL queries. Given a list or queryset of objects:

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

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

### 批量插入

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

    my_band.members.add(me, my_friend)

要优于：

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

...where `Band` and `Artist` are models with a many-to-many relationship.

When inserting different pairs of objects into `ManyToManyField` or when the custom `through` table is defined, use `bulk_create()` method to reduce the number of SQL queries. For example:

    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)

...where `Band` and `Artist` are models with a many-to-many relationship.

当从 `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` 有多对多关系。