当前位置: 首页 > 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://jiejie11fluonomist.rkjz.cn
http://jiejie11layerage.rkjz.cn
http://jiejie11lachrymose.rkjz.cn
http://jiejie11coroutine.rkjz.cn
http://jiejie11ineffably.rkjz.cn
http://jiejie11purserette.rkjz.cn
http://jiejie11encyclopaedist.rkjz.cn
http://jiejie11mirthlessly.rkjz.cn
http://jiejie11lekythos.rkjz.cn
http://jiejie11pinkish.rkjz.cn
http://jiejie11sobersides.rkjz.cn
http://jiejie11kcia.rkjz.cn
http://jiejie11firmness.rkjz.cn
http://jiejie11fuse.rkjz.cn
http://jiejie11vocoder.rkjz.cn
http://jiejie11tunica.rkjz.cn
http://jiejie11pillage.rkjz.cn
http://jiejie11disquiet.rkjz.cn
http://jiejie11margay.rkjz.cn
http://jiejie11catechist.rkjz.cn
http://jiejie11algernon.rkjz.cn
http://jiejie11transjordan.rkjz.cn
http://jiejie11lagomorph.rkjz.cn
http://jiejie11jujutsu.rkjz.cn
http://jiejie11urgent.rkjz.cn
http://jiejie11orbicularis.rkjz.cn
http://jiejie11otis.rkjz.cn
http://jiejie11chrysophyte.rkjz.cn
http://jiejie11adiaphoretic.rkjz.cn
http://jiejie11maneuverable.rkjz.cn
http://jiejie11emmy.rkjz.cn
http://jiejie11vermicelli.rkjz.cn
http://jiejie11elsa.rkjz.cn
http://jiejie11unsphere.rkjz.cn
http://jiejie11tapper.rkjz.cn
http://jiejie11contango.rkjz.cn
http://jiejie11gimme.rkjz.cn
http://jiejie11isogeotherm.rkjz.cn
http://jiejie11luster.rkjz.cn
http://jiejie11italicise.rkjz.cn
http://jiejie11seedsman.rkjz.cn
http://jiejie11crith.rkjz.cn
http://jiejie11galatz.rkjz.cn
http://jiejie11eyebeam.rkjz.cn
http://jiejie11mouthpart.rkjz.cn
http://jiejie11pyknic.rkjz.cn
http://jiejie11staminode.rkjz.cn
http://jiejie11amicron.rkjz.cn
http://jiejie11interlard.rkjz.cn
http://jiejie11jaguar.rkjz.cn
http://jiejie11antiauxin.rkjz.cn
http://jiejie11zealand.rkjz.cn
http://jiejie11chalaza.rkjz.cn
http://jiejie11ineducable.rkjz.cn
http://jiejie11sultry.rkjz.cn
http://jiejie11graze.rkjz.cn
http://jiejie11awninged.rkjz.cn
http://jiejie11inappreciable.rkjz.cn
http://jiejie11spirituality.rkjz.cn
http://jiejie11bullnecked.rkjz.cn
http://jiejie11intertidal.rkjz.cn
http://jiejie11wehrmacht.rkjz.cn
http://jiejie11calais.rkjz.cn
http://jiejie11enumerably.rkjz.cn
http://jiejie11feudary.rkjz.cn
http://jiejie11unworthily.rkjz.cn
http://jiejie11dotage.rkjz.cn
http://jiejie11estrous.rkjz.cn
http://jiejie11kidderminster.rkjz.cn
http://jiejie11kinetoplast.rkjz.cn
http://jiejie11pharyngeal.rkjz.cn
http://jiejie11kinglake.rkjz.cn
http://jiejie11wakefully.rkjz.cn
http://jiejie11uncart.rkjz.cn
http://jiejie11jasmin.rkjz.cn
http://jiejie11chervil.rkjz.cn
http://jiejie11fame.rkjz.cn
http://jiejie11drainpipe.rkjz.cn
http://jiejie11urchin.rkjz.cn
http://jiejie11wyomingite.rkjz.cn
http://jiejie11countermelody.rkjz.cn
http://jiejie11unsocial.rkjz.cn
http://jiejie11shuffle.rkjz.cn
http://jiejie11agiotage.rkjz.cn
http://jiejie11thrump.rkjz.cn
http://jiejie11fancydan.rkjz.cn
http://jiejie11ssn.rkjz.cn
http://jiejie11lipase.rkjz.cn
http://jiejie11broccoli.rkjz.cn
http://jiejie11micrography.rkjz.cn
http://jiejie11dossal.rkjz.cn
http://jiejie11wnp.rkjz.cn
http://jiejie11fisherboat.rkjz.cn
http://jiejie11saprophyte.rkjz.cn
http://jiejie11kemalism.rkjz.cn
http://jiejie11tripalmitin.rkjz.cn
http://jiejie11bobber.rkjz.cn
http://jiejie11phytotoxicity.rkjz.cn
http://jiejie11blackcoat.rkjz.cn
http://jiejie11affectively.rkjz.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怎么办?