queryset 惰性求值是为了安全高效组合查询逻辑,调用 filter() 等方法仅克隆实例并修改 query 对象,不触达数据库;只有 bool()、len()、print()、索引取值等操作才会触发实际查询,且结果缓存后不再刷新。

QuerySet 惰性求值不是“为了显得高级”,而是为了让你能安全、高效地组合查询逻辑——只要你不取数据,它就真不查库。
为什么调用 filter() 不会立刻查数据库
Django 在 filter() 内部只做两件事:克隆当前 QuerySet 实例、往它的 query 对象里追加 WHERE 条件。整个过程不碰数据库连接,也不生成结果集。源码里 self._result_cache 保持为 None,就是“还没查”的明确标记。
-
Book.objects.filter(title__icontains="web")返回的只是一个带描述的查询计划,类似写好但没运行的 SQL 字符串 - 连续调用
.filter().exclude().order_by(),只是反复克隆 + 修改query,最终仍只对应一条 SELECT 语句 - 如果你中途把同一个
QuerySet赋值给多个变量,它们各自独立,互不影响(因为每次_clone()都新建 query 副本)
哪些操作会偷偷触发查询(最容易误判的求值点)
看似无害的操作,实际可能拉全表或执行 COUNT —— 尤其在调试或模板渲染时容易踩坑:
提供使用 django-unfold (v0.56+) 构建高级 Django 管理界面的专业指导。当用户提及 django-unfold、unfold admin 等内容时使用此技能。
-
if qs:触发bool()求值 → 执行SELECT EXISTS(...)(Django 4.2+ 优化过,但旧版本是SELECT * LIMIT 1) -
qs[0]触发查询 → 取第一行,等价于qs.first();但qs[5:10]不触发,只是设置 LIMIT/OFFSET -
print(qs)或repr(qs)会触发__repr__()→ 调用list(qs[:20]),查前 20 条 -
len(qs)必须走完整 COUNT 查询,比qs.exists()重得多;后者生成EXISTS子句,快一个数量级
QuerySet 缓存机制带来的副作用
一旦某个 QuerySet 被求值(比如 for 循环遍历),结果会缓存在 _result_cache 里。后续再迭代它,直接返回缓存,不再查库 —— 这是优点,也是陷阱:
- 如果数据库被其他进程修改,已缓存的
QuerySet不会自动刷新,qs[0].title拿到的是旧值 -
qs = MyModel.objects.filter(x=1); list(qs); qs.filter(y=2)—— 后者是全新 QuerySet,跟前面缓存无关;但qs.filter(y=2).first()会重新查库 - 想强制重查?只能新建 QuerySet:
MyModel.objects.filter(x=1, y=2),别依赖链式调用后的“续查”
真正难处理的不是“怎么让它惰着”,而是你总在某处不经意地写了 list(qs)、qs.count() 或模板里用了 {{ qs|length }},然后发现接口变慢了——这时候得翻 connection.queries 看它到底执行了几条 SQL,而不是猜“是不是 filter 写多了”。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










