Django ORM
问题
Django ORM 的 QuerySet 是怎么工作的?有哪些性能注意点?
答案
Model 定义
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField(unique=True)
class Meta:
ordering = ["name"]
indexes = [models.Index(fields=["email"])]
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name="books")
published = models.DateField()
price = models.DecimalField(max_digits=8, decimal_places=2)
QuerySet 惰性求值
核心特性
QuerySet 是惰性的——构建查询不会立即执行 SQL,只有在迭代、切片、len()、bool() 时才真正查询数据库。
# 以下 3 行不执行任何 SQL
qs = Book.objects.filter(published__year=2024)
qs = qs.exclude(price__gt=100)
qs = qs.order_by("-published")
# 此时才执行 SQL
books = list(qs)
查询优化
# select_related:JOIN 加载外键(一对一/多对一)
books = Book.objects.select_related("author").all()
# 生成 1 条 SQL:SELECT ... FROM book JOIN author ...
# prefetch_related:子查询加载(多对多/反向外键)
authors = Author.objects.prefetch_related("books").all()
# 生成 2 条 SQL:SELECT ... FROM author; SELECT ... FROM book WHERE author_id IN (...)
# only / defer:控制加载的字段
Book.objects.only("title", "price") # 只加载指定字段
Book.objects.defer("content") # 延迟加载大字段
# values / values_list:返回字典/元组,跳过模型实例化
Book.objects.values("title", "price")
Book.objects.values_list("title", flat=True)
聚合与注解
from django.db.models import Count, Avg, F, Q
# 聚合
Book.objects.aggregate(avg_price=Avg("price"))
# {'avg_price': Decimal('45.00')}
# 注解:给每个 author 添加 book_count 字段
authors = Author.objects.annotate(book_count=Count("books"))
for a in authors:
print(a.name, a.book_count)
# F 表达式:引用字段值,在数据库层面计算
Book.objects.update(price=F("price") * 1.1) # 涨价 10%
# Q 对象:复杂条件组合
Book.objects.filter(Q(price__lt=20) | Q(author__name="Alice"))
常见面试问题
Q1: select_related 和 prefetch_related 的区别?
答案:
| 方法 | SQL 策略 | 适用关系 | SQL 数量 |
|---|---|---|---|
select_related | JOIN | ForeignKey, OneToOne | 1 条 |
prefetch_related | IN 子查询 | ManyToMany, 反向外键 | 2 条 |
Q2: 如何检测 N+1 问题?
答案:
# django-debug-toolbar 面板显示 SQL 数量
# 或者使用 django-query-inspector
LOGGING = {
"loggers": {
"django.db.backends": {
"level": "DEBUG",
"handlers": ["console"],
},
},
}
更多 N+1 排查方法参考场景题 - ORM N+1 问题。
Q3: Django ORM 和 SQLAlchemy 怎么选?
答案:
- Django ORM:与 Django 深度集成,迁移工具好用,适合 Django 项目
- SQLAlchemy:更灵活,支持复杂查询、异步、多数据库,适合非 Django 项目或需要精细控制 SQL 的场景
Q4: 如何执行原生 SQL?
答案:
# 方式一:raw()——返回模型实例
users = User.objects.raw("SELECT * FROM auth_user WHERE id = %s", [user_id])
# 方式二:connection.cursor()——完全原生
from django.db import connection
with connection.cursor() as cursor:
cursor.execute("UPDATE users SET active = %s WHERE last_login < %s", [False, cutoff])
安全提醒
始终使用参数化查询(%s 占位符),永远不要拼接字符串构建 SQL。
Q5: QuerySet 的缓存机制?
答案:
QuerySet 在首次求值后会缓存结果。重复迭代同一 QuerySet 不会再次查询:
qs = Book.objects.all()
list(qs) # 执行 SQL,结果缓存到 qs._result_cache
list(qs) # 使用缓存,不再查询
# 注意:切片会创建新 QuerySet,不使用缓存
qs[0] # 新查询:SELECT ... LIMIT 1