Django ORM(Object-Relational Mapping,对象关系映射)允许开发者使用 Python 操作关系型数据库,而不必直接编写 SQL。
ORM 将数据库中的表映射为 Python Model,将数据库记录映射为 Model 实例,并通过 QuerySet 构建和执行数据库查询。
1. 基础查询
| Django ORM | SQL |
|---|---|
Model.objects.all() | SELECT * FROM table |
Model.objects.get(id=1) | SELECT * FROM table WHERE id=1 |
Model.objects.filter(name="A") | SELECT * FROM table WHERE name='A' |
Model.objects.first() | SELECT ... LIMIT 1 |
Model.objects.last() | 按排序取最后一条 |
Model.objects.none() | 返回空 QuerySet |
主要区别:
all():返回所有记录组成的 QuerySet。filter():返回满足条件的 QuerySet,可能包含零条或多条记录。get():返回单个 Model 对象,不是 QuerySet。first()、last():返回单个对象或None。none():返回不包含记录的 QuerySet。
注意:get() 查不到记录时抛出 DoesNotExist,匹配多条时抛出 MultipleObjectsReturned。
另外,get() 并不等同于简单的 LIMIT 1。Django 通常会使用带有限制的查询获取足够多的结果,以判断是否匹配了多条记录。
2. WHERE 条件
Django 使用双下划线 __ 表示字段查找条件(Field Lookup)。
| Django ORM | SQL / 含义 |
|---|---|
filter(id=1) | WHERE id = 1 |
filter(id__exact=1) | WHERE id = 1 |
filter(id__gt=1) | WHERE id > 1 |
filter(id__gte=1) | WHERE id >= 1 |
filter(id__lt=10) | WHERE id < 10 |
filter(id__lte=10) | WHERE id <= 10 |
filter(id__in=[1,2,3]) | WHERE id IN (1,2,3) |
filter(name__contains="a") | 包含字符串 a |
filter(name__icontains="a") | 忽略大小写的包含匹配 |
filter(name__startswith="A") | 以 A 开头 |
filter(name__endswith="A") | 以 A 结尾 |
filter(name__iexact="abc") | 忽略大小写的精确匹配 |
filter(name__isnull=True) | WHERE name IS NULL |
filter(name__isnull=False) | WHERE name IS NOT NULL |
exclude(id=1) | 排除 id=1 的记录 |
filter(price__range=(10,100)) | WHERE price BETWEEN 10 AND 100 |
常用 Lookup 的含义:
exact:精确匹配。contains:包含。icontains:忽略大小写的包含匹配。iexact:忽略大小写的精确匹配。gt、gte:大于、大于等于。lt、lte:小于、小于等于。in:属于指定集合。isnull:判断是否为 SQL NULL。
注意:icontains 等大小写不敏感查询的实际行为可能受到数据库排序规则(Collation)影响。
日期查询
假设模型存在 created_at 字段:
| Django ORM | 含义 |
|---|---|
filter(created_at__year=2026) | 查询 2026 年的数据 |
filter(created_at__month=10) | 查询 10 月的数据 |
filter(created_at__date=date(2026,10,25)) | 查询指定日期 |
filter(created_at__gte=start) | 查询指定时间之后的数据 |
filter(created_at__range=(start,end)) | 查询指定时间范围内的数据 |
实际开发中,如果需要查询某一天的所有记录,通常也可以使用左闭右开的时间范围:
Model.objects.filter(
created_at__gte=start,
created_at__lt=end,
)
这在使用 DateTimeField 时尤其常见。
3. AND / OR / NOT 条件
from django.db.models import Q
Q 用于构建复杂查询条件,支持 &(AND)、|(OR)、~(NOT)。
| Django ORM | SQL |
|---|---|
filter(a=1, b=2) | WHERE a=1 AND b=2 |
filter(Q(a=1) & Q(b=2)) | WHERE a=1 AND b=2 |
filter(Q(a=1) | Q(b=2)) | WHERE a=1 OR b=2 |
filter(~Q(a=1)) | 排除 a=1 |
filter(Q(a=1) & ~Q(b=2)) | a=1 且不满足 b=2 |
复杂条件示例:
Model.objects.filter(
Q(name__icontains="Tokyo")
| Q(name__icontains="Kyoto"),
score__gte=80,
)
对应逻辑:
WHERE
(name LIKE '%Tokyo%' OR name LIKE '%Kyoto%')
AND score >= 80
注意:
- 多个普通关键字参数默认使用 AND。
Q可以嵌套组合,复杂表达式推荐加括号。filter()中混用Q和关键字参数时,Q参数必须放在前面。- 对可空字段取反时,SQL NULL 的处理需要特别注意,不能在所有情况下简单理解为
NOT (字段=值)。
4. SELECT:指定返回字段
| Django ORM | SQL / 功能 |
|---|---|
values("id","name") | SELECT id,name |
values_list("id","name") | 返回元组组成的 QuerySet |
values_list("id",flat=True) | 返回单字段值组成的 QuerySet |
only("id","name") | 仅立即加载指定模型字段 |
defer("description") | 延迟加载指定字段 |
distinct() | SELECT DISTINCT |
values 与 only 的区别
# 返回字典
Model.objects.values("id", "name")
# 返回 Model 实例
Model.objects.only("id", "name")
values() 不构造普通 Model 实例,而 only() 返回 Model 实例,只是延迟加载部分字段。
对于延迟字段,如果以后访问它,可能触发额外的 SQL 查询。
5. ORDER BY
| Django ORM | SQL |
|---|---|
order_by("id") | ORDER BY id ASC |
order_by("-id") | ORDER BY id DESC |
order_by("-score","id") | ORDER BY score DESC, id ASC |
order_by("?") | 随机排序 |
Model.objects.order_by("-score", "id")
优先按 score 降序排列,同分时按 id 升序排列。
注意:随机排序可能在大数据量下产生明显的性能开销。
6. LIMIT / OFFSET
| Django ORM | SQL |
|---|---|
[:10] | LIMIT 10 |
[10:20] | LIMIT 10 OFFSET 10 |
[20:30] | LIMIT 10 OFFSET 20 |
示例:
Model.objects.order_by("id")[10:20]
建议分页时使用稳定排序,防止结果顺序不确定。
对于大规模数据,较大的 OFFSET 可能导致查询效率下降,可以考虑基于主键或游标的分页。
7. Aggregate:聚合函数
from django.db.models import Count, Sum, Avg, Max, Min
| Django ORM | SQL |
|---|---|
aggregate(Count("id")) | SELECT COUNT(id) |
aggregate(Sum("price")) | SELECT SUM(price) |
aggregate(Avg("score")) | SELECT AVG(score) |
aggregate(Max("score")) | SELECT MAX(score) |
aggregate(Min("score")) | SELECT MIN(score) |
示例:
Model.objects.aggregate(
total=Count("id"),
avg_score=Avg("score"),
max_score=Max("score"),
)
返回:
{
"total": 100,
"avg_score": 85.5,
"max_score": 99,
}
aggregate() 返回聚合结果字典,不返回 QuerySet。
8. GROUP BY 与 annotate
from django.db.models import Count, Avg
| Django ORM | SQL |
|---|---|
values("city_id").annotate(total=Count("id")) | GROUP BY city_id |
values("city_id").annotate(avg=Avg("score")) | 按城市计算平均分 |
annotate(total=Count("items")) | 为每个对象计算关联记录数量 |
示例:
POI.objects.values("city_id").annotate(
total=Count("id"),
avg_score=Avg("score"),
)
概念上对应:
SELECT
city_id,
COUNT(id) AS total,
AVG(score) AS avg_score
FROM poi
GROUP BY city_id;
aggregate 与 annotate 的区别
aggregate():对整个查询结果计算统计值,返回字典。annotate():向查询结果中的对象或分组添加计算字段,返回 QuerySet。
HAVING
POI.objects.values("city_id").annotate(
total=Count("id")
).filter(total__gt=10)
概念上对应:
SELECT city_id, COUNT(id) AS total
FROM poi
GROUP BY city_id
HAVING COUNT(id) > 10;
注意:values()、annotate() 和 filter() 的调用顺序可能影响分组方式与聚合结果。
9. JOIN:关联查询
假设存在两个 Model:
class City(models.Model):
name = models.CharField(max_length=100)
class POI(models.Model):
name = models.CharField(max_length=100)
city = models.ForeignKey(
City,
on_delete=models.CASCADE,
related_name="pois",
)
正向关联查询
| Django ORM | SQL / 功能 |
|---|---|
filter(city__name="Tokyo") | JOIN City 并过滤城市名称 |
values("city__name") | JOIN City 并获取城市名称 |
filter(city_id=1) | 直接通过外键 ID 查询 |
select_related("city") | JOIN 并提前加载 City 对象 |
例如:
POI.objects.filter(city__name="Tokyo")
概念上对应:
SELECT poi.*
FROM poi
INNER JOIN city
ON poi.city_id = city.id
WHERE city.name = 'Tokyo';
反向关联查询
City.objects.filter(pois__name="Tokyo Tower")
通过 related_name="pois" 从 City 查询其关联的 POI。
也可以:
city = City.objects.get(id=1)
city.pois.all()
返回该城市关联的全部 POI。
如果未指定 related_name,反向关联管理器默认通常为 poi_set,而反向查询的字段名称通常为 poi。
10. select_related 与 prefetch_related
这是 Django ORM 性能优化中非常重要的两个方法,用于解决 N+1 Query 问题。
N+1 Query 问题
pois = POI.objects.all()
for poi in pois:
print(poi.city.name)
可能产生:
1 次 SQL:查询全部 POI
N 次 SQL:分别查询每个 POI 的 City
如果有 100 个 POI,最坏情况下可能执行 101 次 SQL。
select_related
pois = POI.objects.select_related("city")
通过 SQL JOIN,在同一次查询中加载关联对象。
适用于:
- ForeignKey
- OneToOneField
特点:通常只执行一次 SQL 查询,但 JOIN 可能增加查询结果宽度和复杂度。
prefetch_related
cities = City.objects.prefetch_related("pois")
for city in cities:
for poi in city.pois.all():
print(poi.name)
通常执行两次 SQL:
SQL 1:查询 City
SQL 2:批量查询关联的 POI
随后在 Python 中完成关联关系的组织。
适用于:
- ManyToManyField
- 反向 ForeignKey(一对多)
- ForeignKey
- OneToOneField
对比
| 特性 | select_related | prefetch_related |
|---|---|---|
| 工作方式 | SQL JOIN | 单独查询并在 Python 中关联 |
| SQL 次数 | 通常 1 次 | 通常多次 |
| ForeignKey | 支持 | 支持 |
| OneToOneField | 支持 | 支持 |
| ManyToManyField | 不支持 | 支持 |
| 反向 ForeignKey | 不支持 | 支持 |
| 主要用途 | 单值关联 | 多值关联 |
通常建议:单值关联优先考虑 select_related,多值关联使用 prefetch_related。
自定义 Prefetch
from django.db.models import Prefetch
cities = City.objects.prefetch_related(
Prefetch(
"pois",
queryset=POI.objects.filter(score__gte=80),
to_attr="high_score_pois",
)
)
这样可以只预加载满足条件的 POI,并保存到自定义属性 high_score_pois 中。
11. F 表达式
from django.db.models import F
F 允许直接在数据库中引用字段值,进行计算或字段之间的比较。
更新字段
Product.objects.filter(id=1).update(
stock=F("stock") - 1
)
对应:
UPDATE product
SET stock = stock - 1
WHERE id = 1;
字段之间比较
Product.objects.filter(
sale_price__lt=F("original_price")
)
对应:
WHERE sale_price < original_price
为什么使用 F?
传统写法:
product = Product.objects.get(id=1)
product.stock -= 1
product.save()
这属于先读取、再修改、再写入。
当多个请求同时执行时,可能产生丢失更新的问题。
F() 可以让数据库直接执行字段计算,避免上述简单读改写模式导致的竞态问题。
但它不能代替所有并发控制机制。例如复杂的库存扣减,还需要检查更新条件和受影响的行数。
12. Case / When 条件表达式
from django.db.models import Case, When, Value, CharField
类似 SQL 中的 CASE WHEN。
POI.objects.annotate(
level=Case(
When(score__gte=90, then=Value("high")),
When(score__gte=60, then=Value("medium")),
default=Value("low"),
output_field=CharField(),
)
)
概念上对应:
CASE
WHEN score >= 90 THEN 'high'
WHEN score >= 60 THEN 'medium'
ELSE 'low'
END
适用于动态分类、条件排序、统计分组等场景。
13. Subquery 与 OuterRef
from django.db.models import Subquery, OuterRef
允许在 Django ORM 中构建关联子查询。
假设每个城市有多个 POI,需要取得每个城市评分最高的 POI 名称:
top_poi = POI.objects.filter(
city_id=OuterRef("pk")
).order_by("-score", "id")
cities = City.objects.annotate(
top_poi_name=Subquery(
top_poi.values("name")[:1]
)
)
两个核心概念:
Subquery():将一个 QuerySet 用作 SQL 子查询。OuterRef():引用外层查询中的字段。
这里的 OuterRef("pk") 表示引用外层 City 查询的主键。
类似 SQL:
SELECT
city.*,
(
SELECT name
FROM poi
WHERE poi.city_id = city.id
ORDER BY score DESC, id ASC
LIMIT 1
) AS top_poi_name
FROM city;
适合处理每组最新记录、每组最高分、相关子查询等问题。
14. Exists 表达式
from django.db.models import Exists, OuterRef
例如查询至少拥有一个 POI 的城市:
pois = POI.objects.filter(
city_id=OuterRef("pk")
)
cities = City.objects.filter(
Exists(pois)
)
概念上对应:
SELECT *
FROM city
WHERE EXISTS (
SELECT 1
FROM poi
WHERE poi.city_id = city.id
);
Exists 适合判断关联记录是否存在,无需实际返回关联对象的字段。
15. JSONField 查询
假设:
name_translations = models.JSONField(default=dict)
数据库内容:
{
"en": "Tokyo",
"zh": "东京",
"ja": "東京"
}
常见查询:
| Django ORM | 含义 |
|---|---|
filter(name_translations__en="Tokyo") | en 键的值等于 Tokyo |
filter(name_translations__en__icontains="tok") | en 键的值包含 tok,忽略大小写 |
filter(name_translations__has_key="en") | 是否存在 en 键 |
filter(name_translations__contains={"en":"Tokyo"}) | JSON 中包含指定键值对 |
注意:
name_translations__en是 JSON 键路径查找,而不是普通 SQL JOIN。- JSON 查询的 SQL 实现依赖具体数据库。
- JSONField 的
contains等操作并非所有数据库后端都支持,例如 SQLite 不支持 JSONField 的containslookup。 - 对动态 JSON 键进行大量查询时,需要关注数据库索引和查询性能。
16. exists / count / len
| Django ORM | 功能 |
|---|---|
.exists() | 判断 QuerySet 是否存在记录 |
.count() | 统计记录数量 |
len(queryset) | 获取已求值结果的数量,同时加载结果 |
.first() | 获取第一条记录 |
POI.objects.filter(score__gte=80).exists()
通常生成类似:
SELECT 1
FROM poi
WHERE score >= 80
LIMIT 1;
对尚未加载的大型 QuerySet:
- 只判断是否存在:通常使用
exists()。 - 只需要数量:通常使用
count()。 - 需要遍历全部记录:可直接遍历 QuerySet,避免不必要的重复查询。
注意:如果 QuerySet 已经完整求值并缓存结果,len(queryset) 可以直接利用缓存,不必再执行 SQL。
17. CREATE / UPDATE / DELETE
创建
| Django ORM | SQL |
|---|---|
Model.objects.create(name="A") | INSERT INTO table ... |
obj.save() | 根据对象状态执行 INSERT 或 UPDATE |
get_or_create(...) | 查询,不存在则创建 |
update_or_create(...) | 查询并创建或更新 |
obj, created = Model.objects.get_or_create(
name="Tokyo",
defaults={"score": 80},
)
created 表示本次调用是否创建了新对象。
实际使用中,建议通过数据库唯一约束保证 get_or_create() 对应查找字段的唯一性,以降低并发创建重复记录的风险。
更新
Model.objects.filter(id=1).update(name="A")
对应:
UPDATE table
SET name = 'A'
WHERE id = 1;
update() 直接执行数据库级更新,不会调用 Model 的 save() 方法,也不会触发 pre_save、post_save 信号。
删除
Model.objects.filter(id=1).delete()
概念上对应:
DELETE FROM table
WHERE id = 1;
但需要注意:Django 删除关联数据时,可能根据 on_delete 配置执行额外查询和删除操作。
18. 批量操作
bulk_create
POI.objects.bulk_create([
POI(name="A", city_id=1),
POI(name="B", city_id=1),
POI(name="C", city_id=2),
])
批量插入数据,通常比循环调用 create() 更高效。
bulk_update
pois = list(POI.objects.filter(id__in=[1, 2]))
for poi in pois:
poi.score = 90
POI.objects.bulk_update(
pois,
["score"],
)
批量更新指定字段。
注意:bulk_create() 和 bulk_update() 不调用每个对象的 save(),也不会触发对应的模型保存信号。
对于大量数据,可以使用 batch_size 控制每批操作规模。
19. 事务 Transaction
from django.db import transaction
事务用于保证多个数据库操作作为一个整体执行。
atomic
with transaction.atomic():
Order.objects.create(...)
Product.objects.filter(id=1).update(
stock=F("stock") - 1
)
如果事务内出现导致事务回滚的异常,数据库修改会被整体回滚。
典型应用场景:
- 创建订单并扣减库存。
- 转账操作。
- 同时更新多个存在业务关联的数据表。
select_for_update
with transaction.atomic():
product = Product.objects.select_for_update().get(
id=1
)
if product.stock > 0:
product.stock -= 1
product.save(update_fields=["stock"])
select_for_update() 在支持的数据库中对选中记录加行级锁,通常一直持有到事务结束。
适合需要先读取数据、判断条件、再修改的并发业务。
注意:具体锁行为取决于数据库后端和事务隔离级别。SQLite 不支持通常意义上的 SELECT ... FOR UPDATE 行锁。
20. QuerySet 惰性执行与缓存
Django QuerySet 一个重要特点是 Lazy Evaluation(惰性求值)。
例如:
qs = POI.objects.filter(score__gte=80)
这里通常只是构建 QuerySet,还没有真正执行 SQL。
执行:
for poi in qs:
print(poi.name)
才会真正访问数据库。
常见触发查询的操作
- 遍历 QuerySet。
- 转换为
list()。 - 使用
len()。 - 使用
bool()。 - 访问尚未求值的 QuerySet 的索引。
- 使用
get()、count()、exists()等方法执行数据库查询。
QuerySet 结果缓存
qs = POI.objects.all()
list(qs) # 执行 SQL,并填充 QuerySet 结果缓存
list(qs) # 通常复用缓存
但如果重新构建 QuerySet:
POI.objects.all()
POI.objects.all()
它们是不同的 QuerySet,不会自动共享结果缓存。
iterator
for poi in POI.objects.iterator(chunk_size=2000):
process(poi)
iterator() 适合处理大量数据,避免使用 QuerySet 级别的完整结果缓存。
需要注意,这并不意味着数据库驱动一定逐行获取数据,实际内存与数据获取方式还取决于数据库后端及游标行为。
21. 查询性能优化
1. 解决 N+1 查询
POI.objects.select_related("city")
或者:
City.objects.prefetch_related("pois")
2. 避免查询不必要的字段
POI.objects.values("id", "name")
3. 仅判断是否存在时使用 exists
POI.objects.filter(name="Tokyo").exists()
4. 使用数据库聚合代替 Python 遍历
不推荐:
total = len(list(POI.objects.all()))
通常更合适:
total = POI.objects.count()
5. 合理设置数据库索引
class POI(models.Model):
name = models.CharField(max_length=100)
score = models.IntegerField()
class Meta:
indexes = [
models.Index(fields=["name"]),
models.Index(fields=["-score"]),
]
索引能够加快符合条件的查询,但也会增加存储空间及写入维护成本。
对普通 B-tree 索引而言,icontains 这类前后通配符查询未必能有效利用索引。
6. 使用 explain 分析执行计划
qs = POI.objects.filter(score__gte=80)
print(qs.explain())
explain() 用于获取数据库查询执行计划,辅助判断:
- 是否使用索引。
- 是否执行全表扫描。
- JOIN 是否带来较高成本。
- 是否需要优化筛选或排序条件。
支持的 explain() 选项依赖具体数据库。使用执行分析类选项时需要注意其可能实际执行查询。
22. 其他常用方法
| ORM 方法 | 功能 |
|---|---|
distinct() | 去重 |
reverse() | 反转已有排序 |
union() | 合并两个查询结果集 |
intersection() | 获取结果集交集 |
difference() | 获取结果集差集 |
in_bulk() | 按字段值批量取得对象字典 |
latest("created_at") | 按字段取得最新记录 |
earliest("created_at") | 按字段取得最早记录 |
select_for_update() | 查询并锁定记录 |
using("default") | 指定数据库连接 |
raw() | 执行原生 SQL 查询并返回模型实例 |
union()、intersection()、difference() 对应 SQL 集合操作,字段兼容性、排序和切片等操作存在限制。
23. Django ORM 与 SQL 的关系
Django ORM 并不是 SQL 的替代数据库,而是构建 SQL 的高级抽象。
整体工作流程:
Python ORM
↓
QuerySet 构建
↓
SQL Compiler
↓
SQL + Parameters
↓
数据库驱动
↓
PostgreSQL / MySQL / SQLite
↓
查询结果
↓
Model / Dict / Tuple
可以使用下面的方法查看 QuerySet 生成的 SQL:
qs = POI.objects.filter(
score__gte=80
).order_by("-score")
print(qs.query)
str(qs.query) 适合调试,但不应直接将它当作可以安全重新执行的参数化 SQL。
如果需要查看 SQL 与参数,可以使用:
sql, params = qs.query.sql_with_params()
实际运行中,Django 使用数据库驱动的参数化查询机制处理变量,避免将普通参数直接拼接到 SQL 字符串中。
总结
Django ORM 学习的重点可以分为四个层次:
| 层次 | 重点 |
|---|---|
| 基础操作 | filter、get、exclude、values、order_by |
| SQL 查询能力 | Q、F、annotate、aggregate、JOIN、Subquery |
| 数据操作 | create、update、delete、bulk_create、transaction |
| 性能优化 | select_related、prefetch_related、索引、explain、QuerySet 缓存 |
对于日常 Django 后端开发,应该优先熟悉 filter()、Q()、F()、annotate()、select_related() 和 prefetch_related()。
对于更复杂的数据处理,再进一步学习 Subquery()、OuterRef()、Exists()、事务和数据库锁机制。
Django ORM 可以覆盖大量常见数据库操作,但仍然需要理解 SQL、索引、JOIN、事务和数据库执行计划,才能编写高效且可靠的查询。
参考文档: