.filter().exclude().select_related() 一路链下来,SQL 是什么时候、由谁写出来的?这篇拆开 QuerySet.query,看它的内部结构,再让同一个 Query 在 SQLite 与 PostgreSQL 上各编译一次,并量三个阶段各花多少时间。
type(qs.query) = django.db.models.sql.query.Query
where 节点: WhereNode 子条件: ['IntegerGreaterThan:IntegerGreaterThan(Col(lab_book, lab.Book.price), 10)', "IContains:IContains(Col(lab_book, lab.Book.title), 'x')", "WhereNode:(NOT (AND: Exact(Col(lab_author, lab.Author.name), 'bob')))"]
alias_map(参与查询的表别名): ['lab_book', 'lab_author']
order_by = ('-price',) low_mark/high_mark = (0, 5) select_related = {'author': {}}每次链式调用(filter、exclude …)都是克隆一个 QuerySet 并修改它的 Query。Query 里 where 是一棵 WhereNode 树,各种 lookup 是树上的节点。
[sqlite] SELECT "lab_book"."id", "lab_book"."title", "lab_book"."price", "lab_book"."author_id", "lab_author"."id", "lab_author"."name" FROM "lab_book" INNER JOIN "lab_author" ON ("lab_book"."author_id" = "lab_author"."id") WHERE ("lab_book"."price" > %s AND "lab_book"."title" LIKE %s ESCAPE '\' AND NOT ("lab_author"."name" = %s)) ORDER BY "lab_book"."price" DESC LIMIT 5
params = (10, '%x%', 'bob')
[postgresql] SELECT "lab_book"."id", "lab_book"."title", "lab_book"."price", "lab_book"."author_id", "lab_author"."id", "lab_author"."name" FROM "lab_book" INNER JOIN "lab_author" ON ("lab_book"."author_id" = "lab_author"."id") WHERE ("lab_book"."price" > %s AND UPPER("lab_book"."title"::text) LIKE UPPER(%s) AND NOT ("lab_author"."name" = %s)) ORDER BY "lab_book"."price" DESC LIMIT 5
params = (Int4(10), '%x%', 'bob')SQLCompiler 遍历这棵树,调用每个节点的 as_sql,把 Col、lookup、JOIN、ORDER BY 拼成 SQL,后端(connection.ops、connection.features)决定方言细节。所以同一份 ORM 代码换数据库时,SQL 会变、性能特征也会变。
构建 QuerySet(filter/exclude/select_related/order_by/切片): 65.3 µs 编译成 SQL(get_compiler().as_sql()): 84.3 µs 从头到尾 list(build())(含 SQLite 执行与构造对象): 262.2 µs
构建与编译是纯 Python,没有缓存,每次调用都做一遍。对绝大多数请求这些微秒级开销不是瓶颈;热点路径上可以用 values_list / 原生 SQL / 缓存结果绕开(本文没有测这些替代方案)。
str(qs.query) → SELECT "lab_book"."id", "lab_book"."title", "lab_book"."price", "lab_book"."author_id", "lab_author"."id", "lab_author". ... (参数直接拼进去,只用于阅读,不保证可直接执行) sql_with_params() → params = (10, '%x%', 'bob') CaptureQueriesContext → 实际执行: SELECT "lab_book"."id", "lab_book"."title", "lab_book"."price", "lab_book"."author_id", "lab_author" ... connection.queries 需要 DEBUG=True 或 CaptureQueriesContext
最可靠的是看真实执行的语句(CaptureQueriesContext);str(qs.query) 只适合阅读。