select_related 和 prefetch_related 都能消灭 N+1,差别在哪?各自发出什么 SQL?什么时候只能用哪一个?这篇用同一份数据把两者的查询次数、耗时、SQL 都摆出来。
不优化(每本书访问 author 查一次) 查询 2001 次 213.5 ms
select_related("author") 查询 1 次 9.9 ms
prefetch_related("author") 查询 2 次 8.4 ms耗时是 5 轮取最快的一次。两种方式都把 N+1 变成常数次查询,耗时接近;这里的数据量下差别很小,不代表在别的数据分布下也一样。
不优化 查询 2001 次 236.9 ms
prefetch_related("tags") 查询 2 次 58.2 ms
select_related("tags") → FieldError: Invalid field name(s) given in select_related: 'tags'. Choices are: author多对多(和反向外键)是「一对多」的关系,JOIN 会让主表的行重复,所以只能用 prefetch_related(分两步查再在 Python 里组装)。
select_related:
SELECT "lab_book"."id", "lab_book"."title", "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") LIMIT 2
prefetch_related:
SELECT "lab_book"."id", "lab_book"."title", "lab_book"."author_id" FROM "lab_book" LIMIT 2
SELECT ("lab_book_tags"."book_id") AS "_prefetch_related_val_book_id", "lab_tag"."id", "lab_tag"."name" FROM "lab_tag" INNER JOIN "lab_book_tags" ON ("lab_tag"."id" = "lab_book_tags"."tag_id") WHERE "lab_book_tags"."book_id" IN (1, 2)select_related 一条 JOIN;prefetch_related 一条 IN (…)。IN 列表的长度等于主表这一批的行数,行数很大时要分批(SQLite 有变量个数限制,本文没有测到边界)。
100 个作者各查一次书 查询 101 次 18.4 ms
prefetch_related("books") 查询 2 次 8.4 ms
Prefetch(queryset=filter, to_attr) 的查询数: 2 SQL 2: SELECT "lab_book"."id", "lab_book"."title", "lab_book"."author_id" FROM "lab_book" WHERE ("lab_book"."id" <= 200 AND "lab_book"."author_id" IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1...
前 3 个作者带回的书数量: [2, 2, 2]
过滤后再访问原属性 a.books.all() 会不会多一次查询?
在预取的属性上再 .filter(...) 的查询数(100 个作者): 102用 Prefetch(queryset=..., to_attr=...) 预取带条件的子集;之后读 to_attr 指定的属性。在预取的关系上再写 .filter() 会重新查询,是 N+1 复发的常见原因。