Django-ORM-select_related

  • 作用
  • 使用场景
  • 示例
    • [无 select_related 的查询](#无 select_related 的查询)
    • [有 select_related 的查询](#有 select_related 的查询)
  • 如何理解 "只发起一次查询,包含所有相关作者信息"
    • [1. select_related 的工作原理](#1. select_related 的工作原理)
    • [2. 具体示例解析](#2. 具体示例解析)
    • [3. 为什么只发起一次查询](#3. 为什么只发起一次查询)
  • 数据库中的books量巨大,使用`select_related`导致服务崩掉,如何解决
    • 程序层面优化
      • [1. 优化 select_related 的使用](#1. 优化 select_related 的使用)
      • [2. 限制查询字段](#2. 限制查询字段)
      • [3. 分页(Pagination)](#3. 分页(Pagination))
        • [使用 Django 内置的分页器](#使用 Django 内置的分页器)
        • [使用基于游标的分页(Cursor-based Pagination)](#使用基于游标的分页(Cursor-based Pagination))
      • [4. 批量处理(Batch Processing)](#4. 批量处理(Batch Processing))
      • [5. 使用 iterator](#5. 使用 iterator)
      • [6. 使用 prefetch_related 结合 select_related](#6. 使用 prefetch_related 结合 select_related)
      • [7. 数据库索引优化](#7. 数据库索引优化)
      • [8. 缓存机制](#8. 缓存机制)
      • [9. 异步处理](#9. 异步处理)
    • 数据库层面优化
      • [10. 数据库层面的优化](#10. 数据库层面的优化)

作用

select_related 主要用于优化一对一(OneToOneField)外键(ForeignKey) 关系中的查询。

它通过SQL的JOIN操作,在单个查询中获取相关对象的数据,从而减少数据库查询次数。

使用场景

• 当你需要访问外键或一对一关系的相关对象时。

• 适用于深度较浅的关系(通常一层或两层)。

示例

假设有以下模型:

python 复制代码
from django.db import models

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

class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
python 复制代码
books = Book.objects.all()
for book in books:
    print(book.author.name)  # 每次循环都会发起一次数据库查询

上述代码会对每个 book 对象的 author 执行一次额外的查询,导致"N+1查询问题"。

python 复制代码
books = Book.objects.select_related('author').all()
for book in books:
    print(book.author.name)  # 只发起一次查询,包含所有相关作者信息

通过 select_related,Django 在单个查询中使用JOIN语句同时获取 Book 和对应的 Author 数据,避免了多次查询。

当然,理解 select_related 如何实现"只发起一次查询,包含所有相关作者信息"对于掌握Django的查询优化至关重要。让我们深入探讨这一过程,包括Django如何构建SQL查询、执行查询以及处理结果。


如何理解 "只发起一次查询,包含所有相关作者信息"

select_related 通过SQL JOIN 操作将主查询和相关模型的查询合并为一个单一的数据库查询。这意味着,当你调用 select_related 时,Django会在后台构造一个包含JOIN的SQL语句,一次性从数据库中获取所有需要的数据。

2. 具体示例解析

当你执行 Book.objects.select_related('author').all() 时,Django会生成一个包含JOIN的SQL查询。

sql 复制代码
SELECT book.id, book.title, book.author_id, author.id, author.name
FROM book
INNER JOIN author ON book.author_id = author.id;

这个查询通过 INNER JOINbook 表和 author 表连接起来,一次性获取所有书籍及其对应的作者信息。

Django将上述SQL语句发送到数据库执行。数据库处理JOIN操作并返回一个包含所有书籍和作者信息的结果集。

Django的ORM会将查询结果映射到相应的Python对象中。具体来说:

• 每个 Book 实例都会包含其相关联的 Author 实例。

• 这些 Author 实例已经被预先加载,不需要额外的数据库查询。

因此,当你迭代 books 并访问 book.author.name 时,Django已经拥有了所有必要的数据,直接从内存中获取 author.name,而不会发起新的数据库查询。

3. 为什么只发起一次查询

关键在于 select_related 使用了JOIN操作,将多个表的数据合并到一个结果集中。这意味着:

单一查询 :只需要执行一次SQL查询,就可以获取所有相关的数据。

减少开销 :避免了"N+1查询问题",即避免了对每个 Book 对象都执行一次额外的查询来获取其 Author


数据库中的books量巨大,使用select_related导致服务崩掉,如何解决

程序层面优化

拿时间换空间:

  • 通过加一些条件只在必要的时候使用 select_related
  • 只查询必要字段
  • 分页
  • 分批
  • 迭代器
  • 缓存
  • 异步

如果某些关联数据不需要,可以避免使用 select_related,或者在必要时才使用。

python 复制代码
# 只在需要时使用 select_related
books = Book.objects.all()
for book in books:
    if some_condition(book):
        author = book.author  # 触发单独的查询
        print(author.name)

或者分情况使用 select_related

python 复制代码
books = Book.objects.filter(some_field=some_value).select_related('author')
for book in books:
    print(book.author.name)

2. 限制查询字段

只选择需要的字段,减少每次查询的数据量。

python 复制代码
books = Book.objects.select_related('author').only('title', 'author__name')
for book in books:
    print(book.author.name)

或者使用 valuesvalues_list

python 复制代码
books = Book.objects.select_related('author').values('title', 'author__name')
for book in books:
    print(book['author__name'])

3. 分页(Pagination)

将查询结果分批加载,每次只处理一部分数据,避免一次性加载所有记录。

使用 Django 内置的分页器
python 复制代码
from django.core.paginator import Paginator

books = Book.objects.select_related('author').all()

paginator = Paginator(books, 100)  # 每页100条记录

page_number = 1
while True:
    page = paginator.get_page(page_number)
    if not page:
        break
    for book in page:
        print(book.author.name)
    page_number += 1
使用基于游标的分页(Cursor-based Pagination)

对于大数据量且需要高效分页的场景,基于游标的分页更为适用。

python 复制代码
from django.db import connection

books = Book.objects.select_related('author').order_by('id')  # 确保有排序字段

batch_size = 1000
offset = 0

while True:
    batch = books[offset:offset + batch_size]
    if not batch:
        break
    for book in batch:
        print(book.author.name)
    offset += batch_size

注意 :对于非常大的数据集,建议使用基于游标的分页库,如 django-elasticsearch-dsl 或其他支持高效分页的工具。

4. 批量处理(Batch Processing)

将数据分成较小的批次进行处理,避免一次性加载所有数据。

python 复制代码
from django.db import transaction

batch_size = 1000
books = Book.objects.select_related('author').all()

for i in range(0, books.count(), batch_size):
    batch = books[i:i + batch_size]
    with transaction.atomic():  # 根据需要使用事务
        for book in batch:
            print(book.author.name)

5. 使用 iterator

iterator 方法可以逐批从数据库中获取数据,减少内存消耗。

python 复制代码
books = Book.objects.select_related('author').all().iterator()

for book in books:
    print(book.author.name)

注意 :使用 iterator 后,无法再次遍历查询集,且缓存机制会有所不同。

在某些复杂查询中,可以结合使用 select_relatedprefetch_related 来优化性能。

python 复制代码
books = Book.objects.select_related('author').prefetch_related('other_related_field')
for book in books:
    print(book.author.name)

但对于大数据量,通常建议优先考虑分页或批量处理。

7. 数据库索引优化

确保在 Book 表的 author_id 字段上有索引,以加快 JOIN 操作的速度。

python 复制代码
class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.ForeignKey(Author, on_delete=models.CASCADE, db_index=True)  # 确保有索引

8. 缓存机制

对于不经常变化的数据,可以使用缓存来减少数据库查询次数。

python 复制代码
from django.core.cache import cache

def get_books():
    books = cache.get('all_books')
    if not books:
        books = list(Book.objects.select_related('author').all().iterator())
        cache.set('all_books', books, timeout=60*15)  # 缓存15分钟
    for book in books:
        print(book.author.name)

注意:缓存大数据量可能会占用大量内存,需谨慎使用。

9. 异步处理

将耗时的处理任务放到异步队列中执行,如 Celery,避免阻塞主线程。

python 复制代码
# tasks.py
from celery import shared_task
from .models import Book

@shared_task
def process_books():
    books = Book.objects.select_related('author').all().iterator()
    for book in books:
        print(book.author.name)

# 在视图中调用
process_books.delay()

数据库层面优化

10. 数据库层面的优化

https://blog.csdn.net/2303_78378466/article/details/145123310

分表分库 :将 books 表拆分成多个子表或数据库,减少单个查询的压力。

读写分离 :将读操作和写操作分离到不同的数据库实例,提升查询性能。

使用更高效的数据库:如 PostgreSQL 在处理复杂查询时性能更优,可以考虑切换数据库。

  • 作用
  • 使用场景
  • 示例
    • [无 select_related 的查询](#无 select_related 的查询)
    • [有 select_related 的查询](#有 select_related 的查询)
  • 如何理解 "只发起一次查询,包含所有相关作者信息"
    • [1. select_related 的工作原理](#1. select_related 的工作原理)
    • [2. 具体示例解析](#2. 具体示例解析)
    • [3. 为什么只发起一次查询](#3. 为什么只发起一次查询)
  • 数据库中的books量巨大,使用`select_related`导致服务崩掉,如何解决
    • 程序层面优化
      • [1. 优化 select_related 的使用](#1. 优化 select_related 的使用)
      • [2. 限制查询字段](#2. 限制查询字段)
      • [3. 分页(Pagination)](#3. 分页(Pagination))
        • [使用 Django 内置的分页器](#使用 Django 内置的分页器)
        • [使用基于游标的分页(Cursor-based Pagination)](#使用基于游标的分页(Cursor-based Pagination))
      • [4. 批量处理(Batch Processing)](#4. 批量处理(Batch Processing))
      • [5. 使用 iterator](#5. 使用 iterator)
      • [6. 使用 prefetch_related 结合 select_related](#6. 使用 prefetch_related 结合 select_related)
      • [7. 数据库索引优化](#7. 数据库索引优化)
      • [8. 缓存机制](#8. 缓存机制)
      • [9. 异步处理](#9. 异步处理)
    • 数据库层面优化
      • [10. 数据库层面的优化](#10. 数据库层面的优化)
相关推荐
轻松Ai享生活2 分钟前
5 Python 技巧,让你秒变大神
python
程序员麻辣烫24 分钟前
晋升系列4:学习方法
java·数据库·程序人生·学习方法
颜淡慕潇1 小时前
【面试题系列】 Redis 核心面试题(二)&答案
数据库·redis·缓存
我真的不会C1 小时前
Mysql表的复合查询
java·数据库·mysql
CodeJourney.1 小时前
光储直流微电网:能源转型的关键力量
数据库·人工智能·算法·能源
王嘉俊9251 小时前
MySQL 入门笔记
数据库·笔记·sql·mysql·adb
时雨h1 小时前
Spring MVC 详细分层和微服务
java·数据结构·数据库·sql
百香果果ccc2 小时前
Maven的依赖管理
java·数据库·maven
小技工丨2 小时前
Flink之SQL join
数据库·sql·flink
Ronin-Lotus2 小时前
深度学习篇---Opencv中的机器学习和深度学习
python·深度学习·opencv·机器学习