QuerySet 到底什么时候查库?第二次迭代还查吗?count() 和 len() 有什么区别?这篇用 CaptureQueriesContext 把每个操作发了几次查询、发的是什么 SQL 都数出来。
构造 + 链式调用 filter/order_by/exclude 查询 0 次 第 1 次 list(qs) 查询 1 次 第 2 次 list(qs)(结果已缓存) 查询 0 次 len(qs)(已缓存) 查询 0 次 qs[0](已缓存) 查询 0 次 qs.count()(已缓存) 查询 0 次 qs.exists()(已缓存) 查询 0 次
惰性:只有「求值」才查询(迭代、切片带步长、list()、len()、bool() 等)。求值一次后结果缓存在 _result_cache,同一个对象的后续访问不再查库。
Item.objects.all()[0] 1 次: SELECT "lab_item"."id", "lab_item"."name", "lab_item"."price" FROM "lab_item" LIMIT 1 Item.objects.all()[5:10] 并迭代 1 次: SELECT "lab_item"."id", "lab_item"."name", "lab_item"."price" FROM "lab_item" LIMIT 5 OFFSET 5 qs.count() 1 次: SELECT COUNT(*) AS "__count" FROM "lab_item" WHERE "lab_item"."price" > 10 qs.exists() 1 次: SELECT 1 AS "a" FROM "lab_item" WHERE "lab_item"."price" > 10 LIMIT 1 bool(qs) 1 次: SELECT "lab_item"."id", "lab_item"."name", "lab_item"."price" FROM "lab_item" WHERE "lab_item"."price" > 10 len(qs) 1 次: SELECT "lab_item"."id", "lab_item"."name", "lab_item"."price" FROM "lab_item" WHERE "lab_item"."price" > 10 repr(qs)(比如在 shell 里回车) 1 次: SELECT "lab_item"."id", "lab_item"."name", "lab_item"."price" FROM "lab_item" LIMIT 21
判断「有没有」用 exists()(LIMIT 1),不要 bool(qs) 或 len(qs)(会取回全部行)。只要个数用 count()。
先 qs.exists() 查询 1 次 再 list(qs) 查询 1 次 再 qs.exists()(有缓存了) 查询 0 次 新 qs:先 if qs2: (bool) 查询 1 次 再 qs2.count()(缓存) 查询 0 次 新 qs:qs3.iterator() 遍历一遍 查询 1 次 再 list(qs3)(iterator 不填充缓存) 查询 1 次
一个常见的写法 if qs.exists(): for x in qs: 发两次查询;如果本来就要迭代,直接 list(qs) 再判断更省。
第一次 list(qs): [] 新建一条之后再 list(qs)(同一个对象): [] qs.all() 生成新的 QuerySet 再取: [<Item: Item object (51)>] qs.filter() 之后: [<Item: Item object (51)>]
长期持有的 QuerySet(模块级变量)会一直返回第一次的结果,这是个常见 bug。要「重新查」就要生成新的 QuerySet。