---
title: "数据库访问优化"
version: 5.2
locale: zh-hans
source: https://docs.djangoproject.com/zh-hans/5.2/topics/db/optimization/
canonical: https://djangodocs.dev/zh-hans/5.2/topics/db/optimization/
---
# 数据库访问优化

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

## 首先性能分析

作为一般的编程实践，这是不言而喻的。找出 [你在做什么查询以及它们的成本](/zh-hans/5.2/faq/models/#faq-see-raw-sql-queries)。使用 [`QuerySet.explain()`](/zh-hans/5.2/ref/models/querysets/#django.db.models.query.QuerySet.explain) 来了解你的数据库如何执行特定的 `QuerySet`。你可能还想使用像 [django-debug-toolbar](https://pypi.org/project/django-debug-toolbar/) 这样的外部项目，或者直接监控你的数据库的工具。

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

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

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

……包括：

- [Indexes](https://en.wikipedia.org/wiki/Database_index) 。这是第一优先级，在你从性能分析中确定应该添加哪些索引 *之后*。这是第一优先级的。使用 [`Meta.indexes`](/zh-hans/5.2/ref/models/options/#django.db.models.Options.indexes) 或 [`Field.db_index`](/zh-hans/5.2/ref/models/fields/#django.db.models.Field.db_index) 从 Django 添加这些索引。可以考虑使用 `filter()`、`exclude()`、`order_by()` 等方式为你经常查询的字段添加索引，因为索引可能有助于加快查询速度。请注意，确定最好的索引是一个复杂的数据库依赖性话题，将取决于你的特定应用。维护索引的开销可能会超过查询速度的任何收益。

- 合理使用字段类型。

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

## 理解 `QuerySet`

理解 [QuerySets](/zh-hans/5.2/ref/models/querysets/) 是用简单代码获得高效率的关键。特别是在：

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

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

- [QuertSets 是惰性的](/zh-hans/5.2/topics/db/queries/#querysets-are-lazy)。
- 当 [它们被计算时](/zh-hans/5.2/ref/models/querysets/#when-querysets-are-evaluated)。
- 不过 [数据保存在内存中](/zh-hans/5.2/topics/db/queries/#caching-and-querysets)。

### 理解缓存属性

除了整个 `QuerySet` 的缓存之外，还对 ORM 对象上的属性结果进行了缓存。通常，不可调用的属性会被缓存。例如，假设 [示例博客模型](/zh-hans/5.2/topics/db/queries/#queryset-model-example)：

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

但一般来说，可调用属性每次都会导致数据库查询：

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

阅读模板代码时要注意——模板系统不允许使用括号，但会自动调用可调用对象代码，隐藏了上述区别。

小心使用你自己的自定义属性——在需要的时候由你自己来实现缓存，例如使用 [`cached_property`](/zh-hans/5.2/ref/utils/#django.utils.functional.cached_property) 装饰器。

### 使用 `with` 模板标签

要使用 `QuerySet` 的缓存行为，你可能需要使用 [`with`](/zh-hans/5.2/ref/templates/builtins/#std-templatetag-with) 模板标签。

### 使用 `iterator()`

当你有很多对象时，`QuerySet` 的缓存行为可能会导致大量的内存被使用。在这种情况下，[`iterator()`](/zh-hans/5.2/ref/models/querysets/#django.db.models.query.QuerySet.iterator) 可能会有帮助。

### 使用 `explain()`

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

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

例子：

- 在最基本的层面上，使用 [filter 和 exclude](/zh-hans/5.2/ref/models/querysets/#queryset-api) 在数据库中进行过滤。
- 使用 [`F 表达式`](/zh-hans/5.2/ref/models/expressions/#django.db.models.F) 根据同一模型中的其他字段进行过滤。
- 利用 [注解在数据库中执行聚合](/zh-hans/5.2/topics/db/aggregation/)。

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

### 使用 `RawSQL`

最简单直接的方法是 [`RawSQL`](/zh-hans/5.2/ref/models/expressions/#django.db.models.expressions.RawSQL) 表达式，它允许一些 SQL 显式的添加到查询中。如果这还不够强大：

### 使用原生 SQL

编写你自己的 [自定义 SQL 来检索数据或填充模型](/zh-hans/5.2/topics/db/sql/)。使用 `django.db.connection.query` 找出 Django 为你写的东西，然后从那里开始。

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

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

因此，使用 [示例博客模型](/zh-hans/5.2/topics/db/queries/#queryset-model-example)：

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

会比以下更快：

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

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

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

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

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

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

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

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

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

深入理解 [`select_related()`](/zh-hans/5.2/ref/models/querysets/#django.db.models.query.QuerySet.select_related) 和 [`prefetch_related()`](/zh-hans/5.2/ref/models/querysets/#django.db.models.query.QuerySet.prefetch_related) ，并使用它们：

- 在 [管理器和默认管理器](/zh-hans/5.2/topics/db/managers/) 中使用。请注意管理器何时被使用；有时这很棘手，所以不要做出假设。
- 在视图代码层或其他层中，可能在需要时使用 [`prefetch_related_objects()`](/zh-hans/5.2/ref/models/querysets/#django.db.models.prefetch_related_objects) 。

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

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

当你只想得到字典或列表的值，并且不需要 ORM 模型对象时，可以适当使用 [`values()`](/zh-hans/5.2/ref/models/querysets/#django.db.models.query.QuerySet.values) 。这些对于替换模板代码中的模型对象非常有用——只要你提供的字典具有与模板中使用时相同的属性就行。

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

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

在不进行性能分析的情况下，不要过于激进地推迟字段，因为数据库必须从磁盘读取大多数非文本、非 `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()](/zh-hans/5.2/topics/db/queries/#topics-db-queries-update) 使用批量 SQL UPDATE 语句。类似地，尽可能使用批量删除（ [bulk deletes](/zh-hans/5.2/topics/db/queries/#topics-db-queries-delete)  ）。

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

### 直接使用外键值

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

```
entry.blog_id
```

替换成：

```
entry.blog.id
```

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

排序是耗时的；对每个字段的排序是数据库必须执行的操作。如果模型有一个默认排序（ [`Meta.ordering`](/zh-hans/5.2/ref/models/options/#django.db.models.Options.ordering) ）并且不需要它，那么可以通过调用没有参数的 [`order_by()`](/zh-hans/5.2/ref/models/querysets/#django.db.models.query.QuerySet.order_by) 在查询集上删除它。

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

## 使用批量方法

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

### 批量创建

当创建对象时，尽可能使用 [`bulk_create()`](/zh-hans/5.2/ref/models/querysets/#django.db.models.query.QuerySet.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`](/zh-hans/5.2/ref/models/querysets/#django.db.models.query.QuerySet.bulk_create) ），因此要确保它适用于你的情况。

### 批量更新

当更新对象时，尽可能使用 [`bulk_update()`](/zh-hans/5.2/ref/models/querysets/#django.db.models.query.QuerySet.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()
```

注意此方法有一些 [`注意事项`](/zh-hans/5.2/ref/models/querysets/#django.db.models.query.QuerySet.bulk_update) ，因此确保它适合你的案例。

### 批量插入

当插入对象到 [`ManyToManyFields`](/zh-hans/5.2/ref/models/fields/#django.db.models.ManyToManyField) 时，使用带有多个对象的 [`add()`](/zh-hans/5.2/ref/models/relations/#django.db.models.fields.related.RelatedManager.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.

当不同的对象对插入到 [`ManyToManyField`](/zh-hans/5.2/ref/models/fields/#django.db.models.ManyToManyField) 或者自定义的 [`through`](/zh-hans/5.2/ref/models/fields/#django.db.models.ManyToManyField.through) 表被定义时，可以使用 [`bulk_create()`](/zh-hans/5.2/ref/models/querysets/#django.db.models.query.QuerySet.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`](/zh-hans/5.2/ref/models/querysets/#django.db.models.query.QuerySet.bulk_create) ），因此要确保它适用于你的案例。

### 批量删除

当从 [`ManyToManyFields`](/zh-hans/5.2/ref/models/fields/#django.db.models.ManyToManyField) 删除对象时，可以使用带有多个对象的 [`remove()`](/zh-hans/5.2/ref/models/relations/#django.db.models.fields.related.RelatedManager.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`](/zh-hans/5.2/ref/models/fields/#django.db.models.ManyToManyField) 里删除不同的对象对时，可以在带有多种 [`through`](/zh-hans/5.2/ref/models/fields/#django.db.models.ManyToManyField.through) 模型实例的 [`Q`](/zh-hans/5.2/ref/models/querysets/#django.db.models.Q) 表达式上使用 [`delete()`](/zh-hans/5.2/ref/models/querysets/#django.db.models.query.QuerySet.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` 有多对多关系。
