Django中select_related查询优化的具体实现

作者:

文章目录
  • 理解 N+1 查询问题及其性能影响 掌握 select_related 的作用和使用方法 能够分析 Django ORM 生成的 SQL 语句 在实际开发中正确应用查询优化技巧
  • 目录
    • 目标
    • 一、问题引入:N+1 查询问题
      • 1.1 数据模型定义
      • 1.2 存在性能问题的代码
      • 1.3 对应的 SQL 查询
    • 二、解决方案:select_related
      • 2.1select_related的基本用法
      • 2.2 优化后的 SQL
    • 三、select_related的深入使用
      • 3.1 深度关联查询
    • 四、实践技巧与注意事项
      • 4.1 查看生成的 SQL 语句
      • 4.2 使用原则
    • 五、总结对比
      • 5.1 性能对比表
      • 5.2 核心要点
      • 5.3 最佳实践

    • 理解 N+1 查询问题及其性能影响
    • 掌握 select_related 的作用和使用方法
    • 能够分析 Django ORM 生成的 SQL 语句
    • 在实际开发中正确应用查询优化技巧

    # models.py
    class Author(models.Model):
        name = models.CharField(max_length=100)
        country = models.CharField(max_length=50)
    
    class Book(models.Model):
        title = models.CharField(max_length=200)
        author = models.ForeignKey(Author, on_delete=models.CASCADE)
        publish_date = models.DateField()
    

    # views.py (存在N+1查询问题)
    books = Book.objects.all()
    for book in books:
        print(f"书名:{book.title}, 作者:{book.author.name}")
    

    -- 第一次查询:获取所有书籍
    SELECT 
        "library_book"."id",
        "library_book"."title", 
        "library_book"."author_id",
        "library_book"."publish_date"
    FROM "library_book";
    
    -- 后续 N 次查询:为每本书单独查询作者
    SELECT "library_author"."id", "library_author"."name", "library_author"."country"
    FROM "library_author" WHERE "library_author"."id" = 1;
    
    SELECT "library_author"."id", "library_author"."name", "library_author"."country"
    FROM "library_author" WHERE "library_author"."id" = 2;
    
    -- ... 依此类推,产生 N+1 次查询
    

    问题分析:如果有 100 本书,将产生 101 次数据库查询,严重影响性能。

    # views.py (优化后的代码)
    books = Book.objects.select_related('author').all()
    for book in books:
        print(f"书名:{book.title}, 作者:{book.author.name}")
    

    SELECT 
        "library_book"."id",
        "library_book"."title",
        "library_book"."author_id", 
        "library_book"."publish_date",
        -- 通过 JOIN 一次性获取作者信息
        "library_author"."id",
        "library_author"."name",
        "library_author"."country"
    FROM "library_book"
    INNER JOIN "library_author" 
        ON ("library_book"."author_id" = "library_author"."id");
    

    优化效果:无论有多少本书,都只执行 1 次 数据库查询。

    扩展数据模型:

    class Country(models.Model):
        name = models.CharField(max_length=50)
        code = models.CharField(max_length=3)
    
    class Author(models.Model):
        name = models.CharField(max_length=100)
        country = models.ForeignKey(Country, on_delete=models.CASCADE)
    
    class Book(models.Model):
        title = models.CharField(max_length=200)
        author = models.ForeignKey(Author, on_delete=models.CASCADE)
        publish_date = models.DateField()
    

    深度关联查询:

    # 一次性获取 Book -> Author -> Country 的所有数据
    books = Book.objects.select_related('author__country').all()
    for book in books:
        print(f"{book.title} - {book.author.name} - {book.author.country.name}")
    

    对应的 SQL:

    SELECT 
        "library_book"."id",
        "library_book"."title",
        "library_book"."author_id",
        "library_book"."publish_date",
        -- 作者表字段
        "library_author"."id",
        "library_author"."name", 
        "library_author"."country_id",
        -- 国家表字段(通过第二次 JOIN)
        "library_country"."id",
        "library_country"."name",
        "library_country"."code"
    FROM "library_book"
    INNER JOIN "library_author" 
        ON ("library_book"."author_id" = "library_author"."id")
    INNER JOIN "library_country" 
        ON ("library_author"."country_id" = "library_country"."id");
    

    方法1:使用 query 属性

    books = Book.objects.select_related('author')
    print(books.query)
    

    方法2:配置 Django SQL 日志
    settings.py 中添加:

    LOGGING = {
        'version': 1,
        'disable_existing_loggers': False,
        'handlers': {
            'console': {
                'level': 'DEBUG',
                'class': 'logging.StreamHandler',
            },
        },
        'loggers': {
            'django.db.backends': {
                'handlers': ['console'],
                'level': 'DEBUG',
            },
        },
    }
    

    1. 适用场景

      • 一对一关系(OneToOneField)
      • 多对一关系(ForeignKey)
      • 需要立即访问关联对象字段时
    2. 不适用场景

      • 多对多关系(ManyToManyField)
      • 反向的多对一关系
      • 对于这些情况,应该使用 prefetch_related
    3. 注意事项

      • 不要过度使用深度关联
      • 考虑查询返回的数据量
      • 只在确实需要访问关联对象时使用

    查询方式 查询次数 适用场景 SQL 类型
    普通查询 N+1 次 不确定是否需要关联对象 简单查询 + 多次关联查询
    select_related 1 次 确定需要访问关联对象的所有字段 JOIN 查询

    1. 解决问题:N+1 查询问题
    2. 实现原理:使用 SQL JOIN 语句一次性加载关联数据
    3. 使用语法Model.objects.select_related('foreign_key_field')
    4. 深度关联:使用双下划线 'field__related_field'
    5. 记忆口诀:“往前拿,用 select_related

    # 好的实践:明确知道需要作者信息
    books = Book.objects.select_related('author').filter(publish_date__year=2023)
    for book in books:
        # 这里会频繁访问 author 的字段
        display_info = f"{book.title} by {book.author.name}"
    
    # 不好的实践:不确定是否需要关联对象
    books = Book.objects.select_related('author').filter(publish_date__year=2023)
    for book in books:
        # 如果这里根本不访问 book.author,就浪费了 JOIN 的性能
        print(book.title)
    

    到此这篇关于Django中select_related查询优化的具体实现的文章就介绍到这了,更多相关Django select_related查询优化内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客!

    您可能感兴趣的文章:

    • 利用Django框架中select_related和prefetch_related函数对数据库查询优化
    • django中的select_related和prefetch_related性能优化分析
    • Django中select_related和prefetch_related的用法与区别详解
    • Python的Django框架中的select_related函数对QuerySet 查询的优化

    站内搜索