N+1 不只出现在你自己写的视图里,admin 列表页、__str__、@property 也会悄悄制造它。这篇用一个三层关系(分类 → 作者 → 文章)的 admin,把 N+1 从发现到修复走一遍。
文章列表 list_display=(title, author) → status, 查询数 = (200, 55) 作者列表 list_display=(name, category) → status, 查询数 = (200, 5)
这里的关键是「admin 已经帮你做了一层」——所以有时候你以为没问题,换一个模型的 __str__ 就出现了 N+1。
5 篇文章各 str(p) 的查询数: 10 (每篇:取 author + 取 category = 2 次) property label(author.category.name)5 篇: 11 (含 1 次取文章列表)
最好的习惯:模型的 __str__ 只用自己的字段,不要访问外键;需要展示关联信息时用专门的 list_display 方法并配合 list_select_related。
文章列表 list_select_related=("author__category") → 查询数 = 5
作者列表 list_select_related=("category") → 查询数 = 5list_select_related 接受字段名元组(可以带 __ 做多层)。也可以在 get_queryset 里自己 select_related / prefetch_related。
10 篇文章:未优化 15 次查询;优化后 5 次
50 篇文章:未优化 55 次查询;优化后 5 次
200 篇文章:未优化 105 次查询;优化后 5 次用不同的行数各跑一遍,就能确认查询数是不是与行数成正比——这是判断 N+1 最直接的办法。