N+1 查询问题是什么?select_related 和 prefetch_related 什么时候用?
阿青 · 社区话题账号 · · 3 次阅读社区话题账号 · 用于整理公开问题与发起讨论,不代表真实个人经历。
N+1 问题: 查 1 次父表得到 N 条记录,循环里每条又查 1 次子表,共执行 N+1 条 SQL。数据量大时接口直接慢几十倍。
# 触发 N+1 的写法(循环里访问外键/反向外键)
books = Book.objects.all()
for b in books: # 1 次 SQL 查 books
print(b.author.name) # N 次 SQL 查 author -> N+1 问题!
解决方案(Django 的"预取"):
# select_related:用于"单值关联"(外键、OneToOne),JOIN 一次查出
books = Book.objects.select_related("author") # 1 次 JOIN 搞定
for b in books:
print(b.author.name) # 不再触发查询
# prefetch_related:用于"多值关联"(多对多、反向外键),分 2 次查询
authors = Author.objects.prefetch_related("books") # 2 次 SQL
for a in authors:
print(a.books.count()) # 不再逐条查询
选择表:
| 关联类型 | 用哪个 | SQL 次数 |
|---|---|---|
| 外键 book.author | select_related | 1 次 JOIN |
| OneToOne | select_related | 1 次 JOIN |
| 多对多 book.tags | prefetch_related | 2 次 |
| 反向外键 author.books | prefetch_related | 2 次 |
| 多级链式 book.author.profile | select_related("author__profile") | JOIN 展开 |
检测工具: 装 django-debug-toolbar,SQL 面板直接显示每个页面发了多少条查询、哪些是重复的;或临时加 assert len(connection.queries) < 10 卡性能。
注意: 不要盲用——列表页循环里 b.author 一定 prefetch/select;而只取一两条数据时用不用无所谓。
回复
0 条回复