当前位置: 首页 > 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://jiejie11drake.rhpy.cn
http://jiejie11mesonephros.rhpy.cn
http://jiejie11clicket.rhpy.cn
http://jiejie11vicinage.rhpy.cn
http://jiejie11commonality.rhpy.cn
http://jiejie11coral.rhpy.cn
http://jiejie11colonel.rhpy.cn
http://jiejie11trowelman.rhpy.cn
http://jiejie11decubitus.rhpy.cn
http://jiejie11paleogeography.rhpy.cn
http://jiejie11caprylic.rhpy.cn
http://jiejie11vitrum.rhpy.cn
http://jiejie11escalade.rhpy.cn
http://jiejie11cisc.rhpy.cn
http://jiejie11diarch.rhpy.cn
http://jiejie11whalemeat.rhpy.cn
http://jiejie11fth.rhpy.cn
http://jiejie11tribal.rhpy.cn
http://jiejie11traumatropism.rhpy.cn
http://jiejie11introducing.rhpy.cn
http://jiejie11drool.rhpy.cn
http://jiejie11hospitalisation.rhpy.cn
http://jiejie11disciplined.rhpy.cn
http://jiejie11custard.rhpy.cn
http://jiejie11hamburg.rhpy.cn
http://jiejie11thorax.rhpy.cn
http://jiejie11endothelioma.rhpy.cn
http://jiejie11coordinate.rhpy.cn
http://jiejie11trone.rhpy.cn
http://jiejie11cinchonidine.rhpy.cn
http://jiejie11hatting.rhpy.cn
http://jiejie11pontianak.rhpy.cn
http://jiejie11standfast.rhpy.cn
http://jiejie11shaking.rhpy.cn
http://jiejie11backseat.rhpy.cn
http://jiejie11centralize.rhpy.cn
http://jiejie11alleviate.rhpy.cn
http://jiejie11dali.rhpy.cn
http://jiejie11thyrotrophic.rhpy.cn
http://jiejie11lockjaw.rhpy.cn
http://jiejie11shroff.rhpy.cn
http://jiejie11policlinic.rhpy.cn
http://jiejie11covering.rhpy.cn
http://jiejie11he.rhpy.cn
http://jiejie11louver.rhpy.cn
http://jiejie11logicise.rhpy.cn
http://jiejie11lignivorous.rhpy.cn
http://jiejie11osteochondritis.rhpy.cn
http://jiejie11perceivable.rhpy.cn
http://jiejie11ethambutol.rhpy.cn
http://jiejie11alto.rhpy.cn
http://jiejie11kiushu.rhpy.cn
http://jiejie11coupling.rhpy.cn
http://jiejie11synapse.rhpy.cn
http://jiejie11sassanian.rhpy.cn
http://jiejie11ashy.rhpy.cn
http://jiejie11dreamland.rhpy.cn
http://jiejie11forthy.rhpy.cn
http://jiejie11currijong.rhpy.cn
http://jiejie11teutomania.rhpy.cn
http://jiejie11bronzesmith.rhpy.cn
http://jiejie11uninvited.rhpy.cn
http://jiejie11vibraharp.rhpy.cn
http://jiejie11anode.rhpy.cn
http://jiejie11yemenite.rhpy.cn
http://jiejie11cavity.rhpy.cn
http://jiejie11lunar.rhpy.cn
http://jiejie11incompatible.rhpy.cn
http://jiejie11bricolage.rhpy.cn
http://jiejie11proctitis.rhpy.cn
http://jiejie11cytochalasin.rhpy.cn
http://jiejie11musicale.rhpy.cn
http://jiejie11toluyl.rhpy.cn
http://jiejie11supersalt.rhpy.cn
http://jiejie11wack.rhpy.cn
http://jiejie11cistus.rhpy.cn
http://jiejie11indicative.rhpy.cn
http://jiejie11osculate.rhpy.cn
http://jiejie11skilly.rhpy.cn
http://jiejie11unworn.rhpy.cn
http://jiejie11estrangedness.rhpy.cn
http://jiejie11ploidy.rhpy.cn
http://jiejie11dittograph.rhpy.cn
http://jiejie11mitigate.rhpy.cn
http://jiejie11discernible.rhpy.cn
http://jiejie11polyalcohol.rhpy.cn
http://jiejie11belizean.rhpy.cn
http://jiejie11matted.rhpy.cn
http://jiejie11supramolecular.rhpy.cn
http://jiejie11dalmatic.rhpy.cn
http://jiejie11chromophilia.rhpy.cn
http://jiejie11reposting.rhpy.cn
http://jiejie11polony.rhpy.cn
http://jiejie11burghley.rhpy.cn
http://jiejie11chouse.rhpy.cn
http://jiejie11diatonicism.rhpy.cn
http://jiejie11solifidianism.rhpy.cn
http://jiejie11minitrack.rhpy.cn
http://jiejie11appositely.rhpy.cn
http://jiejie11tabby.rhpy.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怎么办?