甘子萱 发表于 前天 14:58

Django ORM性能优化

Django 的数据库操作(ORM)虽然方便,但如果使用不当,很容易出现性能问题(比如查询缓慢、数据库压力大)。数据库优化的核心目标是:减少不必要的查询、减少数据传输量、让查询跑得更快。
1、N+1查询问题

当查询包含外键关联的数据时,如果循环获取关联对象,会产生 “1 次主查询 + N 次关联查询” 的低效操作(N 是主查询结果的数量)。
# models.py
class Author(models.Model):
    name = models.CharField(max_length=100)
    age = models.IntegerField()

class Book(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)# 外键关联作者1.1 问题

# 1. 先查询所有书籍(1次查询)
books = Book.objects.all()# SQL: SELECT * FROM book;

# 2. 循环获取每本书的作者(N次查询,N=书籍数量)
for book in books:
    print(book.author.name)# 每次都会触发:SELECT * FROM author WHERE id=?;1.2 select_related

用select_related预加载关联对象,select_related适用于外键、一对一关联,会通过JOIN语句一次性把关联数据查出来。
# 1次查询搞定所有数据(主表+关联表JOIN)
books = Book.objects.select_related('author').all()# SQL: SELECT * FROM book JOIN author ON ...;

# 循环获取作者时,不会再查数据库
for book in books:
    print(book.author.name)# 直接从已加载的数据中获取1.3 prefetch_related

prefetch_related适用于多对多 、反向外键(反向查询)关系。它先执行一次主查询,再执行一次查询来获取所有关联对象,然后在 Python 层进行"连接",效率更高。
class Category(models.Model):
    name = models.CharField(max_length=50)

class Book(models.Model):
    # ... 其他字段
    categories = models.ManyToManyField(Category)# 多对多关联# 用prefetch_related预加载多对多数据
books = Book.objects.prefetch_related('categories').all() #         SELECT ... FROM table1; SELECT ... FROM table2 WHERE id IN (...)

categories = Category.objects.prefetch_related('book_set').all()

for book in books:
    # 不会触发额外查询
    print()2、只获取需要的字段

默认情况下,Book.objects.all()会查询表中所有字段,但很多时候我们只需要其中几个字段
2.1 使用 only() 和 defer()

# 只获取需要的字段
books = Book.objects.only('title', 'publication_date')# 只获取标题和出版日期

# 排除大字段
books = Book.objects.defer('description')# 不获取描述字段(假设是很大的文本字段)2.2 使用 values() 和 values_list()

# 获取字典列表
book_titles = Book.objects.values('title', 'author__name')# 返回 [{'title': '...', 'author__name': '...'}]

# 获取元组列表
book_titles = Book.objects.values_list('title', flat=True)# 返回 ['标题1', '标题2', ...]3、数据库索引

以下情况需要加索引

[*]频繁用filter()、exclude()过滤的字段(比如where author_id=1)
[*]频繁用order_by()排序的字段(比如order by publish_time)
[*]外键字段(Django 会自动加索引)、唯一约束字段(自动加索引)
# models.py
class Book(models.Model):
    title = models.CharField(max_length=200, db_index=True)# 为标题添加索引
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    publication_date = models.DateField()
   
    # 复合索引
    class Meta:
      indexes = [
            models.Index(fields=['author', 'publication_date']),# 复合索引
      ]index_together
class Customer(models.Model):
    first_name = models.CharField(max_length=100, db_index=True)
    last_name = models.CharField(max_length=100, db_index=True)
    email = models.EmailField(unique=True)# 唯一约束自动创建索引
   
    class Meta:
      # 复合索引
      index_together = [
            ['first_name', 'last_name'],
      ]4、批量操作代替循环操作

用bulk_create(批量创建)和bulk_update(批量更新)
4.1 使用 bulk_create() 一次性创建多个对象

# 不好的做法
books = []
for i in range(1000):
    book = Book(title=f"Book {i}", author=some_author)
    book.save()# 每次保存都执行一次INSERT

# 好的做法
books =
Book.objects.bulk_create(books)# 一次性执行批量INSERT4.2 使用 bulk_update() 批量更新对象

# 批量更新
books = Book.objects.filter(publication_date__year=2020)
for book in books:
    book.price = book.price * 0.9# 打9折

Book.objects.bulk_update(books, ['price'])# 一次性批量更新

# 使用F表达式
Book.objects.filter(publication_date__year=2020).update(price=F('price') * 0.8)5、使用连接池

在高并发场景下,为每个请求创建和销毁数据库连接开销很大。使用数据库连接池可以复用连接,显著提升性能。
安装django-db-connection-pool
pip install django-db-connection-poolsettings.py配置
DATABASES = {
    'default': {
      'ENGINE': 'dj_db_conn_pool.backends.mysql',
      'NAME': 'your_db',
      'USER': 'your_user',
      'PASSWORD': 'your_password',
      'HOST': 'localhost',
      'PORT': '3306',
      'OPTIONS': {
            'POOL_SIZE': 20,       # 连接池大小
            'MAX_OVERFLOW': 10,    # 允许超过POOL_SIZE的最大连接数
            'POOL_RECYCLE': 3600,# 连接回收时间(秒)
      }
    }
}6、使用聚合和注解

annotate和 aggregate是 Django ORM 中用于执行数据库聚合操作的两个强大工具,它们允许你在数据库层面进行计算,避免将大量数据拉到 Python 中进行处理,从而显著提升性能。
模型示例:
from django.db import models

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, related_name='books')
    price = models.DecimalField(max_digits=6, decimal_places=2)
    published_date = models.DateField()
    rating = models.IntegerField(choices=[(i, i) for i in range(1, 6)])6.1 annotate()

annotate()为查询集中的每个对象添加计算字段(注解)。
6.1.1 基本用法:为作者添加书籍计数

from django.db.models import Count

# 获取所有作者,并为每个作者添加书籍数量字段
authors = Author.objects.annotate(book_count=Count('books'))

for author in authors:
    print(f"{author.name} 写了 {author.book_count} 本书")6.1.2 多字段注解:计算平均价格和最高评分

from django.db.models import Avg, Max

# 为每个作者添加平均价格和最高评分字段
authors = Author.objects.annotate(
    avg_price=Avg('books__price'),
    max_rating=Max('books__rating')
)

for author in authors:
    print(f"{author.name}: 平均价格 ${author.avg_price:.2f}, 最高评分 {author.max_rating}")6.1.3 过滤后注解:计算特定年份的书籍数量

from django.db.models import Count

# 为每个作者添加2020年出版的书籍数量
authors = Author.objects.annotate(
    books_2020=Count('books', filter=models.Q(books__published_date__year=2020))
)

for author in authors:
    print(f"{author.name} 在2020年出版了 {author.books_2020} 本书")6.1.4 链式注解:复杂计算

from django.db.models import F, Count, Value

# 计算每个作者的平均评分和总价值
authors = Author.objects.annotate(
    book_count=Count('books'),
    total_value=Sum('books__price'),
).annotate(
    avg_rating=Avg('books__rating'),
    value_per_book=F('total_value') / F('book_count')
)

for author in authors:
    print(f"{author.name}: 每本书平均价值 ${author.value_per_book:.2f}")6.2 aggregate()

aggregate()计算整个查询集的统计值,返回一个字典。
6.2.1 基本聚合:计算所有书籍的总价和平均价

from django.db.models import Sum, Avg

# 计算所有书籍的总价和平均价
stats = Book.objects.aggregate(
    total_price=Sum('price'),
    average_price=Avg('price')
)

print(f"所有书籍总价: ${stats['total_price']}")
print(f"平均价格: ${stats['average_price']:.2f}")6.2.2 多字段聚合:最高和最低评分

from django.db.models import Max, Min

# 获取最高和最低评分
rating_stats = Book.objects.aggregate(
    highest_rating=Max('rating'),
    lowest_rating=Min('rating')
)

print(f"最高评分: {rating_stats['highest_rating']}")
print(f"最低评分: {rating_stats['lowest_rating']}")6.2.3 过滤后聚合:特定作者书籍统计

from django.db.models import Count, Avg

# 统计某位作者的书籍
author_stats = Book.objects.filter(
    author__name="J.K. Rowling"
).aggregate(
    book_count=Count('id'),
    avg_rating=Avg('rating')
)

print(f"J.K. Rowling 写了 {author_stats['book_count']} 本书")
print(f"平均评分: {author_stats['avg_rating']:.1f}")7、其他

7.1 用count()和exists()代替全量查询

# 好:直接查数量(高效)
total = Book.objects.count()

# 差:先查所有数据再算长度(低效,尤其数据量大时)
total = len(Book.objects.all())# 好:存在即返回True(查到1条就停止)
has_book = Book.objects.filter(title='Django入门').exists()

# 差:查所有数据再判断(可能查很多条)
has_book = len(Book.objects.filter(title='Django入门')) > 07.2 适当使用原生SQL

如果 ORM 查询太复杂(比如多表复杂 JOIN),可以用原生 SQL:
from django.db import connection

def get_book_stats():
    with connection.cursor() as cursor:
      cursor.execute("""
            SELECT author.name, COUNT(book.id)
            FROM author
            JOIN book ON author.id = book.author_id
            GROUP BY author.id
      """)
      # 获取查询结果((作者名, 书籍数量), ...)
      result = cursor.fetchall()
    return result
来源:豆瓜网用户自行投稿发布,如果侵权,请联系站长删除
页: [1]
查看完整版本: Django ORM性能优化