ORM 生成的 SQL 慢,第一步不是猜,是看执行计划。QuerySet.explain() 一行代码就能做到。这篇在 20 万行的 PostgreSQL 表上,看索引前后计划的变化,以及 analyze 选项。
PostgreSQL(customer 没有索引): Seq Scan on lab_order (cost=0.00..3774.00 rows=199 width=20) Filter: (customer = 42) SQLite: 2 0 216 SCAN lab_order
explain() 返回字符串。PostgreSQL 给出包含成本估算的计划树;SQLite 给的是「SCAN / SEARCH」的简要计划。
customer 上有索引之后:
Bitmap Heap Scan on lab_order (cost=5.84..536.83 rows=199 width=20)
Recheck Cond: (customer = 42)
-> Bitmap Index Scan on order_customer_idx (cost=0.00..5.79 rows=199 width=0)
Index Cond: (customer = 42)
amount__gt=100(amount 没有索引,匹配大多数行):
Seq Scan on lab_order (cost=0.00..3774.00 rows=199921 width=20)
Filter: (amount > 100)加索引后要 ANALYZE 更新统计信息,规划器才会据此选计划。选择性差的条件(匹配大多数行)全表扫描本来就更快。
Bitmap Heap Scan on lab_order (cost=5.84..536.83 rows=199 width=20) (actual time=0.038..0.159 rows=200 loops=1)
Recheck Cond: (customer = 42)
Heap Blocks: exact=200
-> Bitmap Index Scan on order_customer_idx (cost=0.00..5.79 rows=199 width=0) (actual time=0.021..0.021 rows=200 loops=1)
Index Cond: (customer = 42)
Planning Time: 0.048 ms
Execution Time: 0.175 ms
--- verbose + format=json 的第一层键:
['Plan']
--- buffers=True(需要 analyze):
[' Index Cond: (customer = 42)', ' Buffers: shared hit=2', 'Planning Time: 0.010 ms', 'Execution Time: 0.055 ms']analyze=True 让数据库真正执行并统计,对比「估算 rows」与「actual rows」能发现统计信息过期。buffers、verbose、format 是 PostgreSQL 的选项(其它数据库支持的选项集不同,本文没有逐个测)。
explain 只对 QuerySet;count() 不能 explain: AttributeError: 'int' object has no attribute 'explain' 对 annotate 聚合查询的 explain: Finalize GroupAggregate (cost=4038.75..4039.01 rows=2 width=12)
只有返回 QuerySet 的链能 explain();count()、exists() 的计划要自己写等价的 QuerySet 或直接 EXPLAIN。