概述
Django 提供了很多 queryset 方法和管理器来帮助开发者最大限度的去操作数据库,本文收集了其他网站相关链接,总结了尝试优化数据库使用时的步骤。
性能分析工具
查询总是要花费代价的,这个不用多说,那么数据库是如何执行特定的 queryset 的?
请总是记住每次修改都要进行性能分析,确保修改时有效的,有好处的,但有可能是不适用的,甚至带来一些查询困难。
- queryset.explain()
- 或是一个外部项目:dajngo-debug-toolbar
- 或是一个监控数据库的工具
数据库优化技巧
- indexes: 优先级排在第一,可以再 Mata.indexes 或是 Field.db_index
中添加索引,因为索引可以有助于加快查询速度 - 合理使用字段类型
理解 QuerySet
一定要理解 queryset 执行的过程
- Queryset 是惰性的:创建 QuerySet 并不会引发任何数据库活动。你可以将一整天的过滤器都堆积在一起,Django 只会在
QuerySet 被 计算 时执行查询操作。
>>> q = Entry.objects.filter(headline__startswith="What")
>>> q = q.filter(pub_date__lte=datetime.date.today())
>>> q = q.exclude(body_text__icontains="food")
>>> print(q)
虽然这看起来像是三次数据库操作,实际上只在最后一行 (print(q)) 做了一次。
一般来说, QuerySet 的结果直到你 “要使用” 时才会从数据库中拿出。
当你要用时,才通过数据库 计算 出 QuerySet
- 当被计算时
见下方:什么时候 QuerySet 被执行 - 数据保存再内存中
见下方:缓存和QuerySet
什么时候 QuerySet 被执行
QuerySet 本身可以被构造,过滤,切片,或者复制赋值等,是无需访问数据库的。只有在你需要从数据库取出数据或者,向数据库存入数据时才需要访问数据库。
支持用一下方式执行一个 QuerySet :
-
迭代:
for e in Entry.objects.all(): print(e.headline)
-
切片:
正如在 限制 QuerySet 条目数 中所解释的那样,QuerySet 可以使用 Python 的数组切片语法进行切片。 切片一个未执行的 QuerySet 通常会返回另一个未执行的 QuerySet, 但如果使用切片语法的 step 参数,Django 会执行数据库查询,并返回一个列表。 切片一个已经执行过的 QuerySet 也会返回一个列表。
-
Pickle 序列化/缓存
-
repr
当你调用 repr() 时,所在 QuerySet 会被执行。这是为了方便 Python 交互式解释器,所以当你交互式使用 API 时,可以立即看到你的结果。
-
len()
当你调用 len() 时,会执行 QuerySet。正如你所期望的,这将返回结果列表的长度。
-
list()
entry_list = list(Entry.objects.all())
-
bool()
if Entry.objects.filter(headline="Test"): print("There is at least one Entry with the headline Test")
缓存和QuerySet
每个 QuerySet 都带有缓存,尽量减少数据库访问。
新创建的 QuerySet 缓存是空的。一旦要计算 QuerySet 的值,就会执行数据查询,随后,Django 就会将查询结果保存在 QuerySet 的缓存中,并返回这些显式请求的缓存(例如,下一个元素,若 QuerySet 正在被迭代)。后续针对 QuerySet 的计算会复用缓存结果。
-
记住这种缓存行为, 在错误使用时可能会耗损性能:
>>> print([e.headline for e in Entry.objects.all()]) >>> print([e.pub_date for e in Entry.objects.all()]) 这意味着同样的数据库查询会被执行两次,实际加倍了数据库负载。 同时,有可能这两个列表不包含同样的记录,因为在两次请求间,可能有 Entry 被添加或删除了。
-
要避免此问题,保存 QuerySet 并复用它:
>>> queryset = Entry.objects.all() >>> print([p.headline for p in queryset]) # Evaluate the query set. >>> print([p.pub_date for p in queryset]) # Re-use the cache from the evaluation.
-
以下情况不会被缓存,并且会重复查询数据库:
例如,重复的从某个查询结果集对象中取指定索引的对象会每次都查询数据库>>> queryset = Entry.objects.all() >>> print(queryset[5]) # Queries the database >>> print(queryset[5]) # Queries the database again 不过,若全部查询结果集已被检出,就会去检查缓存: >>> queryset = Entry.objects.all() >>> [entry for entry in queryset] # Queries the database >>> print(queryset[5]) # Uses cache >>> print(queryset[5]) # Uses cache 以下例子会填充缓存: >>> [entry for entry in queryset] >>> bool(queryset) >>> entry in queryset >>> list(queryset)
使用唯一索引列来检索单个对象
当使用 unique() 或 db_index 的列来检索单个对象时,有两个原因。首先,由于底层数据库索引的存在,查询的速度会更快。另外,如果多个对象与查找对象相匹配,查询的运行速度可能会慢很多;在列上有一个唯一约束保证这种情况永远不会发生, ID 也是索引的一种,尽量的去 get 唯一资源,不建议使用 filter, 因为 id 通过数据库索引,并且保证是唯一的。。
如:
entry = Entry.objects.get(id=10)
要优于
entry = Entry.objects.filter(id=10).first()
以下操作可能会非常慢:
entry = Entry.objects.get(headline__startswith="News")
首先,headline 没有被索引,这将使得底层数据库获取变慢,并且有可能会报错,以 News开头的可能有多条。
其次,查找不保证只返回一个对象。如果查询匹配多于一个对象,它将从数据库中检索并传递所有对象。如果数据库位于单独的服务器上,那这个损失将更复杂,网络开销和延迟也是一个因素。
检索所有内容使用 QuerySet.select_related() 和 prefetch_related()
深入理解它们并使用他们:
地址:select_related
地址:prefetch_related
不要检索你不需要的东西
使用 QuerySet.values() 和 values_list()
当你只想得到字典或列表的值,并且不需要 ORM 模型对象时,可以适当使用 values() 。这些对于替换模板代码中的模型对象非常有用——只要你提供的字典具有与模板中使用时相同的属性就行。
使用 QuerySet.defer() 和 only()
如果你明确不需要这个数据库列(或在大部分情况里不需要),使用 defer() 和 only() 来避免加载它们。注意如果你使用它们,ORM 将必须在单独的查询中获取它们,如果你不恰当的使用,会让事情变得糟糕。
不要在没有分析的情况下过分使用延迟字段,因为数据库必须从磁盘中读取结果中单行的大部分非文本、非VARCHAR数据,即使它最终只使用的几列。当你不想加载许多文本数据或需要大量处理来转换回 Python的字段, defer() 和 only() 方法最有用。总之,先分析,再优化。
使用 QuerySet.count()
……如果你只想计数,不要使用 len(queryset)。
使用 QuerySet.exists()
……若你只想要确认是否有至少存在一项满足条件的结果,而不是 if queryset。
使用批量方法
- bulk_create()
- bulk_update()