Django QuerySet 优化

时间:2023-02-10 12:57:54

QuerySet

什么时候 QuerySet 被执行

QuerySet 本身可以被构造,过滤,切片,或者复制赋值等,是无需访问数据库的。只有在你需要从数据库取出数据或者,向数据库存入数据时才需要访问数据库

  • 迭代。一个 QuerySet 是可迭代的,当你第一次迭代它时,它就会执行其数据库查询

    注意:如果你想做的只是确定至少一个结果是否存在,不要使用这个。使用 exists() 会更有效。

  • 切片QuerySet 可以使用 Python 的数组切片语法进行切片。切片一个未执行的 QuerySet 通常会返回另一个未执行的 QuerySet,但如果使用切片语法的 step 参数,Django 会执行数据库查询,并返回一个列表。切片一个已经执行过的 QuerySet 也会返回一个列表

    还要注意的是,即使对一个未执行的 QuerySet 进行切片,返回另一个未执行的 QuerySet,也不允许进一步修改它(例如,添加更多的过滤器,或修改排序),因为这不能很好地翻译成 SQL,也没有明确的含义

  • len()。当你调用 len() 时,会执行 QuerySet。正如你所期望的,这将返回结果列表的长度

  • list()。通过调用 list() 强制执行 QuerySet

  • bool()。在布尔语境中测试 QuerySet,如使用 bool()orandif 语句,将导致查询被执行。如果至少有一个结果,则 QuerySetTrue,否则为 False

    注意:如果你只想确定至少一个结果是否存在(而不需要实际的对象),使用 exences() 更高效。

QuertSets 是惰性的

QuerySet 是惰性的 —— 创建 QuerySet 并不会引发任何数据库活动。你可以将一整天的过滤器都堆积在一起,Django 只会在 QuerySet计算 时执行查询操作

缓存和 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带来的问题。

QuerySet是否缓存

查询结果集并不总是缓存结果。当仅计算查询结果集的 部分 时,会校验缓存,若没有填充缓存,则后续查询返回的项目不会被缓存()。特别地说,这意味着使用数组切片或索引的 限制查询结果集 不会填充缓存。

例如,重复的从某个查询结果集对象中取指定索引的对象会每次都查询数据库:

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

数据库访问优化

性能分析

使用 QuerySet.explain() 来了解你的数据库是如何执行特定的 QuerySet 。或者 django-debug-toolbar 工具

使用标准数据库优化技巧

  • 数据库索引
  • 合理使用字段类型

使用 iterator()

QuerySet 的缓存行为可能会导致大量的内存被使用。在这种情况下,iterator()

iterator() 将直接读取结果,而不在 QuerySet 级别做任何缓存(在内部,默认的迭代器调用 iterator() 并缓存返回值)。对于一个只需要访问一次就能返回大量对象的 QuerySet 来说,这可以带来更好的性能,并显著减少内存。

查询放到数据库中执行

检索需要

更多的使用下面函数

  • values和values_list
  • only
  • count
  • exists
  • update和delete 批量处理

批量

当创建对象时,尽可能使用 bulk_create()

当更新对象时,尽可能使用 bulk_update()

 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'])

当插入对象到 ManyToManyFields 时,使用带有多个对象的 add() 来减少 SQL 查询的数量

my_band.members.add(me, my_friend)

当从 ManyToManyFields 删除对象时,可以使用带有多个对象的 remove() 来减少 SQL 查询的数量

my_band.members.remove(me, my_friend)