title: N+1查询:数据库性能的隐形杀手与终极拯救指南
date: 2025/05/06 00:16:30
updated: 2025/05/06 00:16:30
author: cmdragon
excerpt:
N+1查询题目是ORM中常见的性能陷阱,表现为在查询主对象时,对每个关联对象举行单独查询,导致查询次数过多。以博客系统为例,查询10位作者及其文章会产生11次查询。通过Tortoise-ORM的prefetch_related方法,可以将查询优化为2次,显著提升性能。优化后的实现方案包括利用SQL JOIN语句加载关联数据,并联合FastAPI举行实践。进阶优化本领包括多层预加载、选择性字段加载和分页查询联合。常见报错涉及模型注册、连接关闭和字段匹配题目,需针对性解决。
categories:
tags:
- N+1查询题目
- Tortoise-ORM
- 异步预加载
- FastAPI
- 数据库优化
- SQL查询
- 性能分析
扫描二维码
关注大概微信搜一搜:编程智域 前端至全栈交流与成长
探索数千个预构建的 AI 应用,开启你的下一个伟大创意:https://tools.cmdragon.cn/
第一章:明白N+1查询题目本质
1.1 什么是N+1查询题目?
N+1查询是ORM利用过程中常见的性能陷阱。假设我们有一个博客系统,当查询作者列表时,如果每个作者关联了多篇文章,常规查询会先获取N个作者(1次查询),然后为每个作者单独执行文章查询(N次查询),统共产生N+1次数据库查询。
示例场景:
- 数据库包含10位作者
- 每位作者有5篇文章
- 常规查询会产生1(作者)+10(文章)=11次查询
1.2 题目复现与性能影响
利用Tortoise-ORM创建数据模型:- # models.py
- from tortoise.models import Model
- from tortoise import fields
- class Author(Model):
- id = fields.IntField(pk=True)
- name = fields.CharField(max_length=50)
- class Article(Model):
- id = fields.IntField(pk=True)
- title = fields.CharField(max_length=100)
- content = fields.TextField()
- author = fields.ForeignKeyField('models.Author', related_name='articles')
复制代码 题目查询代码示例:- async def get_authors_with_articles():
- authors = await Author.all()
- result = []
- for author in authors:
- articles = await author.articles.all()
- result.append({
- "author": author.name,
- "articles": [a.title for a in articles]
- })
- return result
复制代码 利用EXPLAIN ANALYZE分析查询筹划:- -- 主查询
- EXPLAIN
- ANALYZE
- SELECT "id", "name"
- FROM "author";
- -- 单个作者的文章查询
- EXPLAIN
- ANALYZE
- SELECT "id", "title", "content"
- FROM "article"
- WHERE "author_id" = 1;
复制代码 第二章:prefetch_related异步预加载实战
2.1 预加载机制原理
Tortoise-ORM的prefetch_related利用SQL JOIN语句在单个查询中加载关联数据。对于1:N关系,它通过以下步骤实现:
- 执行主查询获取所有作者
- 收集作者ID列表
- 执行关联查询获取所有相关文章
- 在内存中举行数据关联映射
2.2 优化后的实现方案
完整FastAPI示例:- # main.py
- from fastapi import FastAPI
- from tortoise.contrib.fastapi import register_tortoise
- from pydantic import BaseModel
- app = FastAPI()
- # Pydantic模型
- class ArticleOut(BaseModel):
- title: str
- class AuthorOut(BaseModel):
- id: int
- name: str
- articles: list[ArticleOut]
- class Config:
- orm_mode = True
- # 数据库配置
- DB_CONFIG = {
- "connections": {"default": "postgres://user:pass@localhost/blogdb"},
- "apps": {
- "models": {
- "models": ["models"],
- "default_connection": "default",
- }
- }
- }
- # 路由端点
- @app.get("/authors", response_model=list[AuthorOut])
- async def get_authors():
- authors = await Author.all().prefetch_related("articles")
- return [
- AuthorOut.from_orm(author)
- for author in authors
- ]
- # 初始化ORM
- register_tortoise(
- app,
- config=DB_CONFIG,
- generate_schemas=True,
- add_exception_handlers=True,
- )
复制代码 2.3 执行筹划对比分析
优化后的SQL查询示例:- EXPLAIN
- ANALYZE
- SELECT a.id,
- a.name,
- ar.id,
- ar.title,
- ar.content
- FROM author a
- LEFT JOIN article ar ON a.id = ar.author_id;
复制代码 性能对比指标:
指标优化前 (N=10)优化后查询次数112平均相应时间 (ms)32045网络往返次数112内存占用 (KB)850650第三章:进阶优化与最佳实践
3.1 多层预加载本领
处置惩罚多级关联关系:- await Author.all().prefetch_related(
- "articles__comments", # 文章关联的评论
- "profile" # 作者个人资料
- )
复制代码 3.2 选择性字段加载
优化查询字段选择:- await Author.all().prefetch_related(
- articles=Article.all().only("title", "created_at")
- )
复制代码 3.3 分页与预加载联合
分页查询优化方案:- from tortoise.functions import Count
- async def get_paginated_authors(page: int, size: int):
- return await Author.all().prefetch_related("articles")
- .offset((page - 1) * size).limit(size)
- .annotate(articles_count=Count('articles'))
复制代码 课后Quiz
- 当处置惩罚M:N关系时,应该利用哪个预加载方法?
A) select_related
B) prefetch_related
C) both
D) none
答案:B
M:N关系需要利用prefetch_related,因为select_related仅实用于ForeignKey和OneToOne关系
- 以下哪种情况最适合利用prefetch_related?
A) 查询单个对象及其关联的10条记录
B) 列表页需要显示主对象及其关联的统计数量
C) 需要实时更新的高频写入操作
D) 需要关联5层以上的深度查询
答案:B
当需要批量处置惩罚关联数据时,prefetch_related能显著减少查询次数
常见报错解决方案
报错1:TortoiseORMError: Relation does not exist
- 原因:模型未正确注册或字段名拼写错误
- 解决:
- 检查register_tortoise的models配置
- 验证关联字段的related_name拼写
- 执行数据库迁移命令
报错2:OperationalError: connection closed
- 原因:异步连接未正确关闭
- 解决:
- # 在请求处理完成后手动关闭连接
- @app.middleware("http")
- async def close_connection(request, call_next):
- response = await call_next(request)
- await connections.close_all()
- return response
复制代码 报错3:ValidationError: field required (type=value_error.missing)
- 原因:Pydantic模型与ORM模型字段不匹配
- 解决:
- 检查from_orm方法是否正确利用
- 验证response_model的字段定义
- 确保启用orm_mode配置
环境配置与运行
安装依靠:- pip install fastapi uvicorn tortoise-orm[asyncpg] pydantic
复制代码 启动服务:- uvicorn main:app --reload --port 8000
复制代码 测试端点:- curl http://localhost:8000/authors
复制代码 余下文章内容请点击跳转至 个人博客页面 大概 扫码关注大概微信搜一搜:编程智域 前端至全栈交流与成长,阅读完整的文章:N+1查询:数据库性能的隐形杀手与终极拯救指南 | cmdragon's Blog
往期文章归档:
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |