QuerySet
什么时候 QuerySet
被执行
QuerySet
本身可以被构造,过滤,切片,或者复制赋值等,是无需访问数据库的。只有在你需要从数据库取出数据或者,向数据库存入数据时才需要访问数据库
-
迭代
。一个QuerySet
是可迭代的,当你第一次迭代它时,它就会执行其数据库查询注意:如果你想做的只是确定至少一个结果是否存在,不要使用这个。使用
exists()
会更有效。 -
切片
。QuerySet
可以使用 Python 的数组切片语法进行切片。切片一个未执行的QuerySet
通常会返回另一个未执行的QuerySet
,但如果使用切片语法的step
参数,Django 会执行数据库查询,并返回一个列表。切片一个已经执行过的QuerySet
也会返回一个列表还要
注意
的是,即使对一个未执行的QuerySet
进行切片,返回另一个未执行的QuerySet
,也不允许进一步修改它(例如,添加更多的过滤器,或修改排序),因为这不能很好地翻译成 SQL,也没有明确的含义 -
len()
。当你调用len()
时,会执行QuerySet
。正如你所期望的,这将返回结果列表的长度 -
list()
。通过调用list()
强制执行QuerySet
-
bool()
。在布尔语境中测试QuerySet
,如使用bool()
、or
、and
或if
语句,将导致查询被执行。如果至少有一个结果,则QuerySet
为True
,否则为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
来说,这可以带来更好的性能,并显著减少内存。
查询放到数据库中执行
- 在最基本的层面上,使用 filter 和 exclude 在数据库中进行过滤。
- 使用
F 表达式
根据同一模型中的其他字段进行过滤。 - 利用 注解在数据库中执行聚合。
检索需要
更多的使用下面函数
- 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)