当前位置: 首页 > news >正文

Django模型查询与性能调优:告别N+1问题

一、查询基础

QuerySet 详解

Django 中通过模型类的 Manager 构建 QuerySet 来检索数据库对象,其核心特性包括:

  • 代表数据库中对象的集合
  • 可通过过滤器缩小查询范围
  • 具有惰性执行特性(仅在需要结果时才执行 SQL)

常用过滤器

  • all():返回所有对象
  • filter(**kwargs):返回满足条件的对象
  • exclude(** kwargs):返回不满足条件的对象
  • get(**kwargs):返回单个匹配对象(无匹配或多匹配会抛异常)
  • 切片
# 切片操作示例:返回前5个对象(LIMIT 5)
Book.objects.all()[:5]

一对多关联查询

假设一个作者可以写多本书,但每本书只能属于一个作者。

from django.db import modelsclass Author(models.Model):first_name = models.CharField(max_length=100)last_name = models.CharField(max_length=100)def __str__(self):return f"{self.first_name} {self.last_name}"class Book(models.Model):title = models.CharField(max_length=100)publication_date = models.DateField()# 外键关联Author,级联删除,反向查询名为booksauthor = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')def __str__(self):return self.title

正向查询(通过外键属性访问)

b = Book.objects.get(id=2)
b.author  # 获取关联的Blog对象,查询数据库
b.author = some_body  # 设置关联对象
b.save()  # 保存更改

使用 select_related() 预加载关联对象,避免额外查询

b = Book.objects.select_related().get(id=2)
print(b.author)  # 已预加载到缓存,使用缓存,不查询数据库

反向查询(通过关联管理器)

# 未定义related_name, 默认Manager名称为:<模型名称小写>_set
a = Author.objects.get(id=1)
a.book_set.all()  # 返回所有关联的Book# 定义了related_name='books'
a.books.all()  # 更直观的访问方式

关联对象操作方法如下。所有 “反向” 操作对数据库都是立刻生效,保存到数据库。

  • add(obj1, obj2):添加关联对象
  • create(**kwargs):创建并关联新对象
  • remove(obj1, obj2):移除关联对象
  • clear():清空所有关联
  • set(objs):替换关联集合
a = Author.objects.get(id=1)
a.books.set([b1, b2]) #  b1 和 b2 都是 Book 实例

多对多关联查询

假设一个作者可以写多本书,一本书也可以有多个作者。

from django.db import modelsclass Author(models.Model):name = models.CharField(max_length=100)email = models.EmailField()def __str__(self):return self.nameclass Book(models.Model):title = models.CharField(max_length=200)publication_date = models.DateField()# 多对多关联Authorauthors = models.ManyToManyField(Author, related_name='books')def __str__(self):return self.title

正向与反向查询示例

# 正向查询
b = Book.objects.get(id=3)
b.authors.all() # 获取所有关联的Author
b.authors.count()
b.authors.filter(name__contains="张三")# 反向查询
a = Author.objects.get(id=5)
a.book_set.all()  # 获取所有关联的Book

多对多关联中,add()、set() 和 remove() 可直接使用主键

a = Author.objects.get(id=5)
a.book_set.set([b1, b2])
# 等价于
a.book_set.set([b1.pk, b2.pk])

二、N+1查询问题

问题分析

N+1 查询是常见的性能问题,表现为主查询后执行 N 次额外查询。例如:

books = Book.objects.all()
for book in books:print(book.author.first_name)

以上代码会产生 1 次查询获取所有 Book,加上 N 次查询获取对应的 Author(N 为 Book 数量),共 N+1 次查询。

检测方法

  • Django Debug Toolbar:直观显示请求中的 SQL 查询
  • 日志记录:配置日志记录 SQL 语句
  • 性能分析工具:如 Django Silk 分析查询性能

解决方案

方法 1:使用 select_related

适用于一对多(正向)和一对一关系,通过 SQL JOIN 预加载关联对象

  • 语法:select_related('related_field')related_field 是模型中定义的 ForeignKeyOneToOneField 字段
books = Book.objects.select_related('author').all()
for book in books:print(book.author.first_name) # 无额外查询 

可结合 only() 选择需要的字段

books = Book.objects.select_related('author').only('title', 'author__name')

支持多级关联

# 加载书籍、作者及作者家乡信息
books = Book.objects.select_related('author__hometown').all()
for book in books:print(book.author.hometown.name)  # 无额外查询

方法 2:使用 prefetch_related

适用于多对多和反向关系,通过批量查询后在 Python 中关联。适用场景:

  • 多对多关系(ManyToManyField)
  • 反向一对多关系
  • 反向一对一关系
books = Book.objects.prefetch_related('authors').all()
for book in books:print(book.authors.all())  # 无额外查询

参考资料:Django 数据库访问优化

三、高级查询优化

values()

返回字典形式的查询集(返回一个 ValuesQuerySet 对象,其中每个元素是一个字典),适合提取特定字段

books = Book.objects.values('title', 'author')
for book in books:print(book) # 输出示例
{'title': 'Book1', 'author': 'Author1'}
{'title': 'Book2', 'author': 'Author2'}

values_list()

返回元组形式的查询集(返回一个 ValuesListQuerySet 对象,其中每个元素是一个元组),内存占用更低

books = Book.objects.values_list('title', 'author')
for book in books:print(book)### 输出示例
('Book1', 'Author1')
('Book2', 'Author2')

使用 flat=True 获取单一字段值列表。如果有多个字段时,传入 flat 会报错。

titles = Book.objects.values_list('title', flat=True)
# <QuerySet ['红楼梦', '西游记', ...]>

使用 named=True ,结果返回 namedtuple()

books_info = Book.objects.values_list("id", "title", named=True)
# <QuerySet [Row(id=1, title='红楼梦'), ...]>

values()和values_list()对比

对比维度 values() values_list()
返回值类型 返回一个包含字典的查询集,字典的键为字段名,值为字段对应的数据 返回一个包含元组的查询集,元组中的元素依次对应指定字段的值
内存占用 相对较高,因为字典需要存储键值对信息 通常更节省内存,元组是更轻量的数据结构,无需存储字段名
使用场景 适合需要通过字段名访问字段值的场景,例如需要明确知道每个值对应的字段时 适合仅需要获取字段值的场景,例如只需批量获取某个或某几个字段的具体数据时

Q() 对象复杂查询

Q() 对象用于构建复杂查询条件,支持逻辑运算

  • &:逻辑与(AND)
  • |:逻辑或(OR)
  • ~:逻辑非(NOT)
from django.db.models import Q# 标题含Python或作者为John的书籍
books = Book.objects.filter(Q(title__icontains="Python") | Q(author="John")
)# 复杂组合条件
books = Book.objects.filter((Q(title__icontains="Python") | Q(title__icontains="Django")) &~Q(author="John")
)

查看生成的 SQL

调试时可查看 QuerySet 生成的 SQL

queryset = Book.objects.filter(author="John")
print(queryset.query)  # 输出对应的SQL语句

四、项目实战

场景

Django+Vue 后台管理系统中,一般需要支持不同的数据权限

  • 仅本人数据权限
  • 本部门及以下数据权限
  • 本部门数据权限
  • 指定部门数据权限
  • 全部数据权限

image-20250801113536987

数据权限与功能权限(基于RBAC实现)的区别

  • 功能权限:控制 “能做什么”(如新增、删除按钮的显示和执行)
  • 数据权限:控制 “能看到什么数据”(如销售经理只能查看自己团队的数据)

实战

使用Q() 对象构建复杂查询,实现灵活的数据权限计算

image-20250801113855216

点击查看完整代码


您正在阅读的是《Django从入门到实战》专栏!关注不迷路~


文章转载自:
http://jiejie11lace.nzmw.cn
http://jiejie11liverwort.nzmw.cn
http://jiejie11trypsinization.nzmw.cn
http://jiejie11carcinoma.nzmw.cn
http://jiejie11elbowroom.nzmw.cn
http://jiejie11rick.nzmw.cn
http://jiejie11commemoration.nzmw.cn
http://jiejie11strategos.nzmw.cn
http://jiejie11thinkpad.nzmw.cn
http://jiejie11lantern.nzmw.cn
http://jiejie11aestivation.nzmw.cn
http://jiejie11jubilantly.nzmw.cn
http://jiejie11result.nzmw.cn
http://jiejie11tricker.nzmw.cn
http://jiejie11mandinka.nzmw.cn
http://jiejie11babyish.nzmw.cn
http://jiejie11mobilization.nzmw.cn
http://jiejie11pademelon.nzmw.cn
http://jiejie11swbw.nzmw.cn
http://jiejie11monitory.nzmw.cn
http://jiejie11thegn.nzmw.cn
http://jiejie11adagietto.nzmw.cn
http://jiejie11attar.nzmw.cn
http://jiejie11headcheese.nzmw.cn
http://jiejie11luminal.nzmw.cn
http://jiejie11fibroplasia.nzmw.cn
http://jiejie11becalmed.nzmw.cn
http://jiejie11splutter.nzmw.cn
http://jiejie11rejon.nzmw.cn
http://jiejie11malvaceous.nzmw.cn
http://jiejie11waterage.nzmw.cn
http://jiejie11assumed.nzmw.cn
http://jiejie11recremental.nzmw.cn
http://jiejie11tantalate.nzmw.cn
http://jiejie11grosz.nzmw.cn
http://jiejie11beslobber.nzmw.cn
http://jiejie11cauliflower.nzmw.cn
http://jiejie11streuth.nzmw.cn
http://jiejie11indistinct.nzmw.cn
http://jiejie11podium.nzmw.cn
http://jiejie11nobility.nzmw.cn
http://jiejie11tailoress.nzmw.cn
http://jiejie11isopropyl.nzmw.cn
http://jiejie11sybaris.nzmw.cn
http://jiejie11pulpiteer.nzmw.cn
http://jiejie11hercules.nzmw.cn
http://jiejie11bogota.nzmw.cn
http://jiejie11chestertonian.nzmw.cn
http://jiejie11elbert.nzmw.cn
http://jiejie11gardenesque.nzmw.cn
http://jiejie11imbursement.nzmw.cn
http://jiejie11cosey.nzmw.cn
http://jiejie11nipple.nzmw.cn
http://jiejie11piety.nzmw.cn
http://jiejie11lanceolar.nzmw.cn
http://jiejie11neurochemistry.nzmw.cn
http://jiejie11bioflavonoid.nzmw.cn
http://jiejie11fetation.nzmw.cn
http://jiejie11sustentation.nzmw.cn
http://jiejie11affectionately.nzmw.cn
http://jiejie11unharmful.nzmw.cn
http://jiejie11typhoidin.nzmw.cn
http://jiejie11euhemeristic.nzmw.cn
http://jiejie11cheater.nzmw.cn
http://jiejie11decalage.nzmw.cn
http://jiejie11hemolysis.nzmw.cn
http://jiejie11ambitiousness.nzmw.cn
http://jiejie11acclimatize.nzmw.cn
http://jiejie11catchlight.nzmw.cn
http://jiejie11discrete.nzmw.cn
http://jiejie11nidifugous.nzmw.cn
http://jiejie11railer.nzmw.cn
http://jiejie11absolutism.nzmw.cn
http://jiejie11zacharias.nzmw.cn
http://jiejie11felicitator.nzmw.cn
http://jiejie11enfield.nzmw.cn
http://jiejie11levelly.nzmw.cn
http://jiejie11festology.nzmw.cn
http://jiejie11state.nzmw.cn
http://jiejie11loathe.nzmw.cn
http://jiejie11typing.nzmw.cn
http://jiejie11knee.nzmw.cn
http://jiejie11rigidness.nzmw.cn
http://jiejie11feminise.nzmw.cn
http://jiejie11berat.nzmw.cn
http://jiejie11laryngology.nzmw.cn
http://jiejie11vires.nzmw.cn
http://jiejie11taking.nzmw.cn
http://jiejie11underbidden.nzmw.cn
http://jiejie11schrik.nzmw.cn
http://jiejie11accentual.nzmw.cn
http://jiejie11colectomy.nzmw.cn
http://jiejie11hippocentaur.nzmw.cn
http://jiejie11hodgepodge.nzmw.cn
http://jiejie11bilharziosis.nzmw.cn
http://jiejie11yogini.nzmw.cn
http://jiejie11malvasia.nzmw.cn
http://jiejie11jetabout.nzmw.cn
http://jiejie11picnicker.nzmw.cn
http://jiejie11serviette.nzmw.cn
http://www.jiejie11.cn/news/589.html

相关文章:

  • 如何设计并搭建云数据库自动化测试框架
  • P1678 烦恼的高考志愿
  • 从资源闲置到弹性高吞吐,JuiceFS 如何构建 70GB/s 吞吐的缓存池?
  • python中datetime模块
  • 2025-8-1总结
  • 验证MySQL主从切换后的数据一致性是数据库可靠性测试的核心场景
  • 自动化运维基础基础1-使用python3通过telnet访问华为交换机
  • Java国际租房系统源码:解锁高效跨境租赁新模式
  • Java打造同城在线咨询:系统源码与问诊服务升级
  • 人声伴奏分离神器,可离线,一键操作,电脑小白也会用~
  • 超聚变:智能体时代,AI原生重构城企数智化基因
  • Java同城在线咨询系统源码:开启便捷医疗问诊新篇
  • Go 注意事项
  • 小学生算法:我国集成电路设计人才缺口到底有多大??
  • Linux for循环一行代码执行命令
  • 《CLIP改进工作串讲》论文精读笔记 - 教程
  • 通义灵码支持 Qwen3-Coder,带你玩转 Agentic Coding,免费不限量
  • 分布式训练模型1
  • 一个电脑抓包工具
  • springboot项目如何写出优雅的controller?
  • ahk 文件怎么运行(AutoHotkey)
  • 命名空间和作用域
  • 2025最佳Windows优化工具推荐:292KB小工具解决Win11右键菜单/自动更新/Defender等12大痛点
  • idea优化(截图为IntelliJ IDEA 2023.3.6)
  • AcWing 801. 二进制中1的个数
  • C++小白修仙记_LeetCode刷题_28找出字符串中第一个匹配项的下标
  • 虚拟化管理软件libvirt
  • C++ 转身 -- Chrome内核源码学习路线梳理
  • 扫描发票自动录入财务系统怎么做?
  • 扫描发票识别金额老是把1看成7怎么办?