DevCN
菜单
讨论动态发现圈子我关注的我的收藏
# Python

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;而只取一两条数据时用不用无所谓。

下载推广海报

圈内讨论推广海报

《N+1 查询问题是什么?select_related 和 prefetch_related 什么时候用?》完整推广海报
REPLIES

回复

0 条回复
暂无回复。