0

0

如何进行Django的数据库查询优化?

幻影之瞳

幻影之瞳

发布时间:2025-09-06 16:46:43

|

860人浏览过

|

来源于php中文网

原创

答案:Django数据库查询优化的核心是减少查询次数、控制返回数据量、提升查询效率。通过select_related和prefetch_related解决N+1问题,分别用于一对一/多对一和多对多关系;使用only和defer精确控制字段加载;用values和values_list减少模型实例创建开销;count和exists替代len和first避免全量查询;为常用查询字段添加数据库索引,但需权衡写入性能;在ORM表达受限时使用raw或原生SQL执行复杂查询或批量操作,但要注意安全与可移植性。结合Django Debug Toolbar和EXPLAIN分析实际执行计划,持续优化查询性能。

如何进行django的数据库查询优化?

Django数据库查询优化,说白了,就是想方设法让你的应用少跑几次数据库,每次跑的时候少搬点数据回来,并且让数据库找数据更快。这不仅仅是为了响应速度,更是为了减轻数据库服务器的压力,避免它成为整个系统的瓶颈。很多时候,一个看似简单的列表页,背后可能藏着几十上百条不必要的SQL查询,而我们往往在开发初期忽略了这些“小问题”,直到系统负载上来才追悔莫及。

解决方案

要优化Django的数据库查询,核心在于理解ORM的工作机制,并善用其提供的各种工具。这包括但不限于减少查询次数、优化单次查询的数据量、利用数据库索引以及在必要时直接介入SQL。最常见的问题是N+1查询,它通常发生在遍历关联对象时。解决这类问题,

select_related
prefetch_related
是你的两大杀手锏。前者用于一对一和多对一关系(JOIN),后者用于多对多和反向外键(单独查询再Python中合并)。此外,
only
defer
能帮你精确控制加载哪些字段,
annotate
aggregate
则能把一些聚合计算推到数据库层面完成。

如何避免Django N+1查询问题?

N+1查询,这玩意儿真是个大坑,我记得有一次,一个简单的列表页加载奇慢,一查日志,好家伙,几百条SQL,全是遍历关联对象时逐个去数据库里捞数据。这个问题的本质是,当你查询一个对象集合,然后又在循环中访问这些对象的关联字段时,Django ORM会为每个关联对象执行一次新的查询。

举个例子,假设我们有

Book
Author
模型:

class Author(models.Model):
    name = models.CharField(max_length=100)

class Book(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)

如果你这样写:

books = Book.objects.all()
for book in books:
    print(book.title, book.author.name)

这里就会产生N+1问题:首先查询所有

Book
(1条SQL),然后在循环中,每访问
book.author.name
时,都会为这本书的作者再查询一次
Author
(N条SQL)。如果有一百本书,那就是101条SQL!

解决办法很简单,利用

select_related
prefetch_related

对于一对一或多对一关系(如

Book
Author
),使用
select_related

books = Book.objects.select_related('author').all()
for book in books:
    print(book.title, book.author.name)

这条语句会生成一条SQL,通过JOIN操作把

Book
Author
的数据一次性查出来,大大减少了数据库往返次数。

而对于多对多关系或反向外键关系,比如一个

Author
有很多
Book
author.book_set.all()
),或者一个
Book
有多个
Tag
,你就需要
prefetch_related

class Tag(models.Model):
    name = models.CharField(max_length=50)

class Book(models.Model):
    # ...
    tags = models.ManyToManyField(Tag)

# 获取所有书籍及其标签
books = Book.objects.prefetch_related('tags').all()
for book in books:
    print(book.title)
    for tag in book.tags.all():
        print('-', tag.name)

prefetch_related
会执行两条SQL查询:一条查
Book
,一条查
Tag
,然后在Python层面将它们关联起来。它避免了循环中对每个
book.tags.all()
都进行一次数据库查询。理解这两种方法的区别和适用场景,是优化N+1问题的关键。

除了N+1,还有哪些常见的Django查询性能瓶颈?如何精确控制查询返回的数据量?

N+1固然是头号公敌,但还有其他一些坑,同样会拖慢你的应用。比如,查询返回了太多不必要的字段,或者进行了不必要的聚合计算。

西安龙昌光学元件企业网站1.1
西安龙昌光学元件企业网站1.1

在原有基础上进行了较大改动进行了代码重写,页面结构和数据库结构均作了优化,基本功能: 1. 精美flash导入页面; 2. 产品发布,支持一级分类; 3. 公司简介、售后服务、联系我们,可进行后台管理; 4. 也可以照“公司简介”的方法增加其他内容,如企业文化、企业荣誉... 5. 采用eWebEditor是网站后台具有强大的编辑功能; 初始帐号: admin 初始密码: admin888

下载
  1. 加载过多字段:

    only()
    defer()
    很多时候,我们只关心模型对象的几个字段,但默认情况下,Django会把所有字段都从数据库里捞出来。这在数据量大的时候,传输成本不容小觑。

    • only('field1', 'field2')
      : 明确指定只加载这些字段。其他未指定的字段在第一次访问时会触发额外的查询。
    • defer('field1', 'field2')
      : 明确指定不加载这些字段。在访问这些被
      defer
      的字段时,才会触发额外的查询。 我个人更倾向于使用
      only
      ,因为它强迫你思考到底需要什么,避免了隐式加载的风险。比如,一个用户列表页,你可能只需要用户的
      username
      email
      ,而不需要他的
      bio
      profile_picture_data
      这种大字段。
    users = User.objects.only('username', 'email').all()
    for user in users:
        print(user.username, user.email)
        # print(user.bio) # 访问 bio 会触发新的查询
  2. 不需要模型对象,只需要特定数据:

    values()
    values_list()
    如果你只是想获取一些数据,然后直接用在模板或者API响应中,而不需要完整的Django模型实例(这会带来额外的Python对象创建开销),那么
    values()
    values_list()
    是更好的选择。

    • values('field1', 'field2')
      : 返回字典列表。
    • values_list('field1', 'field2', flat=True)
      : 返回元组列表,如果只有一个字段且
      flat=True
      ,则返回单个值的列表。
    # 返回 [{'username': 'foo', 'email': 'foo@example.com'}, ...]
    user_data = User.objects.values('username', 'email')
    # 返回 [('foo', 'foo@example.com'), ...]
    user_tuples = User.objects.values_list('username', 'email')
    # 返回 ['foo', 'bar', ...]
    usernames = User.objects.values_list('username', flat=True)
  3. 只需要计数或判断是否存在:

    count()
    exists()
    当你只想知道某个查询有多少结果,或者某个条件是否存在匹配项时,千万不要先
    all()
    len()

    • count()
      : 直接在数据库层面执行
      COUNT(*)
      ,效率远高于加载所有对象再计数。
    • exists()
      : 执行
      SELECT 1 ... LIMIT 1
      ,比
      count()
      更轻量,因为一旦找到一个匹配项就立即返回,无需计数。
    # 推荐
    total_users = User.objects.count()
    # 不推荐
    # total_users = len(User.objects.all())
    
    # 推荐
    if User.objects.filter(is_active=True).exists():
        print("有活跃用户")
    # 不推荐
    # if User.objects.filter(is_active=True).first():
    # if User.objects.filter(is_active=True).count() > 0:

这些方法都是在SQL查询执行之前进行优化,从源头减少了数据传输和处理的负担。

什么时候需要考虑数据库索引和原生SQL?

当ORM提供的优化手段都用尽,或者你的查询逻辑复杂到ORM难以高效表达时,就是时候深入到数据库层面,考虑索引和原生SQL了。

  1. 数据库索引: 索引就像书的目录,能让数据库快速定位到需要的数据,而不是全表扫描。对于经常用于过滤(

    WHERE
    子句)、排序(
    ORDER BY
    子句)或连接(
    JOIN
    )的字段,建立索引通常能带来显著的性能提升。

    • 何时添加索引?

      • 外键字段(Django默认会为
        ForeignKey
        自动创建索引)。
      • 经常出现在
        WHERE
        子句中的字段。
      • 经常用于
        ORDER BY
        的字段。
      • 唯一性约束的字段(Django也会自动创建唯一索引)。
    • 如何添加索引?

      • 在模型字段定义时使用

        db_index=True
        name = models.CharField(max_length=100, db_index=True)

      • 在模型

        Meta
        类中使用
        indexes
        选项定义复合索引或特定索引类型:

        class Meta:
            indexes = [
                models.Index(fields=['last_name', 'first_name']),
                models.Index(fields=['-pub_date'], name='pub_date_desc_idx'),
            ]
    • 注意事项: 索引不是越多越好。它们会增加数据库的存储空间,并且在数据写入(INSERT, UPDATE, DELETE)时需要额外维护,反而可能降低写入性能。所以,要根据实际的查询模式和数据更新频率进行权衡。使用数据库的

      EXPLAIN
      命令分析查询计划,是判断索引是否生效和是否需要新索引的黄金法则。

  2. 原生SQL:

    raw()
    execute()
    尽管Django ORM功能强大,但总有它力所不及或者效率不佳的场景。比如,非常复杂的聚合查询、存储过程调用、或者一些数据库特有的高级功能。

    • Manager.raw(raw_query, params=None)
      如果你需要执行一个返回模型实例的自定义SQL查询,
      raw()
      方法非常有用。它会返回一个
      RawQuerySet
      ,你可以像操作普通
      QuerySet
      一样迭代它,并且结果会映射到你的模型字段。这对于那些ORM难以表达的复杂
      SELECT
      语句尤其方便。

      # 假设你想执行一个复杂的JOIN和WHERE
      for p in Person.objects.raw('SELECT * FROM myapp_person WHERE first_name = %s', ['John']):
          print(p.first_name)
    • connection.cursor().execute(sql, params=None)
      当你的SQL查询不需要返回模型实例,比如执行
      UPDATE
      DELETE
      INSERT
      语句,或者调用存储过程,甚至只是获取一些聚合值,直接使用数据库连接的游标执行原生SQL是最直接的方式。

      from django.db import connection
      
      def update_some_data():
          with connection.cursor() as cursor:
              cursor.execute("UPDATE myapp_product SET price = price * 1.1 WHERE category = %s", ['Books'])
              # 或者获取一些统计数据
              cursor.execute("SELECT COUNT(*) FROM myapp_order WHERE status = 'pending'")
              row = cursor.fetchone()
              print(f"Pending orders: {row[0]}")
    • 何时使用原生SQL?

      • ORM生成的SQL效率低下或不符合预期。
      • 需要利用数据库特有的高级功能(如地理空间查询、窗口函数等)。
      • 执行批量数据修改操作,避免ORM的逐条更新开销。
      • 调用存储过程。
    • 风险与权衡: 使用原生SQL意味着你放弃了ORM带来的大部分便利和安全性(如SQL注入防护需要自己小心处理参数)。它也降低了代码的可移植性,因为SQL语句可能与特定数据库方言绑定。所以,这应该是最后的手段,在确保没有其他ORM优化方案后才考虑。

总而言之,数据库查询优化是一个持续的过程,没有一劳永逸的解决方案。它需要你深入理解Django ORM的机制,熟悉数据库的基本原理,并结合实际的业务场景和数据访问模式进行分析和调整。多用Django Debug Toolbar,多看数据库日志,多分析

EXPLAIN
结果,才能真正做到“心中有数”。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
数据分析工具有哪些
数据分析工具有哪些

数据分析工具有Excel、SQL、Python、R、Tableau、Power BI、SAS、SPSS和MATLAB等。详细介绍:1、Excel,具有强大的计算和数据处理功能;2、SQL,可以进行数据查询、过滤、排序、聚合等操作;3、Python,拥有丰富的数据分析库;4、R,拥有丰富的统计分析库和图形库;5、Tableau,提供了直观易用的用户界面等等。

749

2023.10.12

SQL中distinct的用法
SQL中distinct的用法

SQL中distinct的语法是“SELECT DISTINCT column1, column2,...,FROM table_name;”。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

328

2023.10.27

SQL中months_between使用方法
SQL中months_between使用方法

在SQL中,MONTHS_BETWEEN 是一个常见的函数,用于计算两个日期之间的月份差。想了解更多SQL的相关内容,可以阅读本专题下面的文章。

350

2024.02.23

SQL出现5120错误解决方法
SQL出现5120错误解决方法

SQL Server错误5120是由于没有足够的权限来访问或操作指定的数据库或文件引起的。想了解更多sql错误的相关内容,可以阅读本专题下面的文章。

1283

2024.03.06

sql procedure语法错误解决方法
sql procedure语法错误解决方法

sql procedure语法错误解决办法:1、仔细检查错误消息;2、检查语法规则;3、检查括号和引号;4、检查变量和参数;5、检查关键字和函数;6、逐步调试;7、参考文档和示例。想了解更多语法错误的相关内容,可以阅读本专题下面的文章。

361

2024.03.06

oracle数据库运行sql方法
oracle数据库运行sql方法

运行sql步骤包括:打开sql plus工具并连接到数据库。在提示符下输入sql语句。按enter键运行该语句。查看结果,错误消息或退出sql plus。想了解更多oracle数据库的相关内容,可以阅读本专题下面的文章。

861

2024.04.07

sql中where的含义
sql中where的含义

sql中where子句用于从表中过滤数据,它基于指定条件选择特定的行。想了解更多where的相关内容,可以阅读本专题下面的文章。

581

2024.04.29

sql中删除表的语句是什么
sql中删除表的语句是什么

sql中用于删除表的语句是drop table。语法为drop table table_name;该语句将永久删除指定表的表和数据。想了解更多sql的相关内容,可以阅读本专题下面的文章。

423

2024.04.29

C++ 设计模式与软件架构
C++ 设计模式与软件架构

本专题深入讲解 C++ 中的常见设计模式与架构优化,包括单例模式、工厂模式、观察者模式、策略模式、命令模式等,结合实际案例展示如何在 C++ 项目中应用这些模式提升代码可维护性与扩展性。通过案例分析,帮助开发者掌握 如何运用设计模式构建高质量的软件架构,提升系统的灵活性与可扩展性。

14

2026.01.30

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PostgreSQL 教程
PostgreSQL 教程

共48课时 | 8.1万人学习

Django 教程
Django 教程

共28课时 | 3.7万人学习

MySQL 教程
MySQL 教程

共48课时 | 2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号