N+1查询可通过django-debug-toolbar的SQL面板识别:若出现多条相同SELECT语句(如重复查询author表20次),即表明存在N+1问题;应据外键方向选用select_related(正向/一对一,用JOIN)或prefetch_related(反向/多对多,用额外查询),误用会加剧性能恶化。

直接上结论:N+1 查询不是“有没有”,而是“在哪、多严重、怎么切”。用 django-debug-toolbar 定位,用 select_related 或 prefetch_related 消除,但选错方法反而拖慢查询。
怎么一眼看出页面正在执行 N+1 查询?
别猜,看 SQL 日志。在开发环境启用 django-debug-toolbar,打开页面后点 “SQL” 面板——如果看到一模一样的 SELECT ... FROM "author" 重复执行了 20 次,而你只查了 20 个 Book 对象,基本就是 N+1 了。
常见触发场景:
- 模板里循环渲染
{{ book.author.name }},但视图没预取author - 序列化器中访问
book.tags.all(),但没对tags做预加载 - API 返回列表时,每个对象都调用
.get_absolute_url(),而该方法内部又查了关联模型
select_related 和 prefetch_related 到底该用哪个?
关键看外键方向和关系类型:
提供使用 django-unfold (v0.56+) 构建高级 Django 管理界面的专业指导。当用户提及 django-unfold、unfold admin 等内容时使用此技能。
-
select_related用在 单值正向/反向外键或一对一(如Book.author、Author.profile),它用JOIN一次查出所有字段,适合深度浅、关联少的场景 -
prefetch_related用在 多对多、反向一对多、或需要自定义查询逻辑(如Book.tags.all()、Author.books.all()),它发额外的SELECT IN查询,避免笛卡尔积爆炸 - 错误示范:
Book.objects.select_related('tags')会报错,因为tags是多对多字段,不支持select_related
性能提示:嵌套太深(比如 select_related('author__profile__user'))可能让单条 SQL 变得臃肿,不如拆成两层 prefetch_related 更可控。
为什么加了 prefetch_related 还是慢?
常见陷阱不止一个:
- 预取字段写错:比如模型定义的是
related_name='books',却写了prefetch_related('book_set')—— 正确应为prefetch_related('books') - 过滤被忽略:默认
prefetch_related('tags')会拉全部标签,如果只需要tags.filter(is_active=True),得用Prefetch显式控制:Prefetch('tags', queryset=Tag.objects.filter(is_active=True)) - 序列化器干扰:DRF 的
SerializerMethodField或to_representation里又访问了未预取的字段,等于白做 - 分页前就 prefetch:对
Book.objects.all().prefetch_related('author')先[:10]再预取,Django 仍会对全部结果预取 —— 必须先切片再预取,或用paginator分页对象
N+1 最难的不是修复,是发现它藏在哪一层:可能是中间件里悄悄查了用户权限,可能是日志装饰器里读了请求 IP 对应的地区信息,也可能是缓存失效时顺手查了一堆配置。上线前跑一次 django-silk 或 django-querycount,比靠经验更可靠。

















