目标
- 理解 n+1 查询问题及其性能影响
- 掌握
select_related的作用和使用方法 - 能够分析 django orm 生成的 sql 语句
- 在实际开发中正确应用查询优化技巧
一、问题引入:n+1 查询问题
1.1 数据模型定义
# 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()
1.2 存在性能问题的代码
# views.py (存在n+1查询问题)
books = book.objects.all()
for book in books:
print(f"书名:{book.title}, 作者:{book.author.name}")
1.3 对应的 sql 查询
-- 第一次查询:获取所有书籍
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 次数据库查询,严重影响性能。
二、解决方案:select_related
2.1select_related的基本用法
# views.py (优化后的代码)
books = book.objects.select_related('author').all()
for book in books:
print(f"书名:{book.title}, 作者:{book.author.name}")
2.2 优化后的 sql
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 次 数据库查询。
三、select_related的深入使用
3.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");
四、实践技巧与注意事项
4.1 查看生成的 sql 语句
方法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',
},
},
}
4.2 使用原则
适用场景:
- 一对一关系(onetoonefield)
- 多对一关系(foreignkey)
- 需要立即访问关联对象字段时
不适用场景:
- 多对多关系(manytomanyfield)
- 反向的多对一关系
- 对于这些情况,应该使用
prefetch_related
注意事项:
- 不要过度使用深度关联
- 考虑查询返回的数据量
- 只在确实需要访问关联对象时使用
五、总结对比
5.1 性能对比表
| 查询方式 | 查询次数 | 适用场景 | sql 类型 |
|---|---|---|---|
| 普通查询 | n+1 次 | 不确定是否需要关联对象 | 简单查询 + 多次关联查询 |
select_related | 1 次 | 确定需要访问关联对象的所有字段 | join 查询 |
5.2 核心要点
- 解决问题:n+1 查询问题
- 实现原理:使用 sql join 语句一次性加载关联数据
- 使用语法:
model.objects.select_related('foreign_key_field') - 深度关联:使用双下划线
'field__related_field' - 记忆口诀:“往前拿,用
select_related”
5.3 最佳实践
# 好的实践:明确知道需要作者信息
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查询优化内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论