← 编程返回顶部

Django ORM 常用操作与原理

Django ORM(Object-Relational Mapping,对象关系映射)允许开发者使用 Python 操作关系型数据库,而不必直接编写 SQL。

ORM 将数据库中的表映射为 Python Model,将数据库记录映射为 Model 实例,并通过 QuerySet 构建和执行数据库查询。

1. 基础查询

Django ORMSQL
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 ORMSQL / 含义
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 ORMSQL
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 ORMSQL / 功能
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 ORMSQL
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 ORMSQL
[: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 ORMSQL
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 ORMSQL
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 ORMSQL / 功能
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。

这是 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。

pois = POI.objects.select_related("city")

通过 SQL JOIN,在同一次查询中加载关联对象。

适用于:

  • ForeignKey
  • OneToOneField

特点:通常只执行一次 SQL 查询,但 JOIN 可能增加查询结果宽度和复杂度。

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_relatedprefetch_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 的 contains lookup。
  • 对动态 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 ORMSQL
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、事务和数据库执行计划,才能编写高效且可靠的查询。


参考文档: