前言
ORM是一种强大的数据操作工具。对开发人员来说,熟练使用这个工具对日常功能开发将很有帮助。本文将介绍一些常用场景和ORM技巧。如果有所帮助,请点赞关注支持一下,Thanks♪(・ω・)ノ
有疑问或者其他建议,欢迎评论。
PS: 如果想了解ORM的原理可以看下我的另一篇文章:了解ORM框架看这一篇就够了
1、如何查看数据库交互情况
你在使用ORM工具开发时是否碰到以下类似问题?
- 在 Django ORM 中执行了一个复杂的查询,但是结果不符合预期,你想查看生成的 SQL 查询语句以便调试。
- 某些页面加载速度较慢,你怀疑是由于某些数据库查询导致的,想了解哪些查询耗费了大量时间。
- 你编写了一个复杂的查询,但是它返回了空结果集,你想确认查询是否正确地转换为了 SQL。
- 你的应用执行的某个查询返回了错误的结果,你需要找出是哪个查询导致了问题。
此时你需要一个查看数据库交互的工具。
使用connection.queries
connection.queries
是 Django 中的一个调试工具,它可以用于查看在请求期间发出的所有数据库查询。可以查看当前数据库连接执行的sql语句和执行时间。
PS: connection.queries在Debug=True时才会生效
>>> from django.db import connection
>>> from django.contrib.auth.models import User
>>> User.objects.values('username','id').first()
>>> connection.queries[-1]
{'sql': 'SELECT `auth_user`.`username`, `auth_user`.`id` FROM `auth_user` ORDER BY `auth_user`.`id` ASC LIMIT 1', 'time': '0.043'}
适用无法使用query方法的场景
>>> from django.contrib.auth.models import User
>>> from django.db.models import Count, Q
>>> print(User.objects.aggregate(
total_users=Count('id'),
total_active_users=Count('id', filter=Q(is_active=True)),
).query)
Traceback (most recent call last):
File "<input>", line 3, in <module>
AttributeError: 'dict' object has no attribute 'query'
>>> print(User.objects.aggregate(
total_users=Count('id'),
total_active_users=Count('id', filter=Q(is_active=True)),
))
{'total_users': 232, 'total_active_users': 208}
>>> connection.queries[-1]
{'sql': 'SELECT COUNT(`auth_user`.`id`) AS `total_users`, COUNT(CASE WHEN `auth_user`.`is_active` = 1 THEN `auth_user`.`id` ELSE NULL END) AS `total_active_users` FROM `auth_user`', 'time': '0.042'}
使用django-debug-toolbar工具
相关介绍可参考文章: juejin.cn/post/684490…
这个工具优点是功能强大,相对更重一点,需要做一下配置修改。如果只是对少量ORM语句分析,使用django自带的connection.queries工具也能满足需求。
2、关联对象查询优化
查询关联对象时,使用select_related()
和 prefetch_related()
进行数据库查询可以减少额外的数据库交互,提高性能。
先看看优化前的结果: 每次遍历关联对象都会增加一次数据库IO。
>>> from django.contrib.auth.models import Permission
>>> from django.db import connection
>>> len(connection.queries)
0
>>> [p.content_type.id for p in Permission.objects.all()[:10]]
[1, 1, 1, 1, 28, 28, 28, 28, 111, 111]
>>> len(connection.queries) # 遍历10次关联对象就增加了11次数据库IO
11
再对比下使用select_related查询关联对象的效果:遍历关联对象未增加任何数据库IO。
>>> [p.content_type.id for p in Permission.objects.select_related("content_type").all()[:10]]
[1, 1, 1, 1, 28, 28, 28, 28, 111, 111]
>>> len(connection.queries) # 遍历10次关联对象只增加了1次数据库IO
12
同样的 prefetch_related()
也可以优化关联对象的查询。两种方法的主要区别在于:
-
select_related() :
- 用于一对一或多对一关系。
- 使用 SQL 的
JOIN
操作在单个查询中获取相关对象。 - 适用于在一个查询中获取主对象及其关联对象的情况。
-
prefetch_related() :
- 用于多对多或一对多关系。
- 使用额外的查询来预取关联对象。
3、高级聚合查询
django2.0后支持在聚合函数中应用过滤器条件。这种用法非常强大,它允许在聚合查询中应用更加复杂的条件,而不仅仅是简单地统计所有记录的数量。
>>> from django.contrib.auth.models import User
>>> from django.db.models import Count, Q
>>> print(User.objects.aggregate(
total_users=Count('id'),
total_active_users=Count('id', filter=Q(is_active=True)),
))
{'total_users': 232, 'total_active_users': 208}
以上ORM查询等价的sql如下:
'SELECT COUNT(`auth_user`.`id`) AS `total_users`, COUNT(CASE WHEN `auth_user`.`is_active` = 1 THEN `auth_user`.`id` ELSE NULL END) AS `total_active_users` FROM `auth_user`'
4、具名元组namedtuples
阿里开发手册中指出:
【强制】在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。 说明:1)增加查询分析器解析成本。2)增减字段容易与resultMap配置不一致。3)无用字段增加网络消耗,尤其是text类型的字段。
因此不推荐以下写法:
>>> from django.contrib.auth.models import Group
>>> print(Group.objects.all().query)
SELECT `auth_group`.`id`, `auth_group`.`name` FROM `auth_group`
那如何指定字段查询?
可以使用values和values_list方法
>>> print(Group.objects.values("name").query)
SELECT `auth_group`.`name` FROM `auth_group`
>>> print(Group.objects.values("name").first())
{'name': 'AI'}
>>> print(Group.objects.values_list("name").first())
('AI',)
更推荐使用以下方式,将查询的结果转换成namedtuples。相对于以上两种方式方便多,而且更简洁,可读性好
>>> rst = Group.objects.values_list("name", named=True).first()
>>> rst.name
'AI'
5、条件表达式
- 先看个sql中的条件表达式示例:
SELECT
employee_id,
first_name,
last_name,
salary,
CASE
WHEN salary < 3000 THEN 'Low'
WHEN salary BETWEEN 3000 AND 7000 THEN 'Medium'
ELSE 'High'
END AS salary_level
FROM employees;
对应的django orm实现如下:
from django.db.models import Case, When, Value, CharField
# 假设 Employee 是你的模型类
employees = Employee.objects.annotate(
salary_level=Case(
When(salary__lt=3000, then=Value('Low')),
When(salary__gte=3000, salary__lte=7000, then=Value('Medium')),
default=Value('High'),
output_field=CharField()
)
).values('employee_id', 'first_name', 'last_name', 'salary', 'salary_level')
- 详情可参考:django官方文档
6、创建新字段
extra()
和annotate()
的区别:
extra()
:
- 是较老的查询方法,在较早版本的Django中使用
- 允许在查询中添加自定义的SQL片段
- 性能相对较低
- 不太推荐在新代码中使用
- 灵活但不够安全,容易引入SQL注入风险
- 主要用于在查询中添加复杂的自定义选择、where条件或排序
annotate()
:
- 是Django推荐的现代查询方法
- 使用QuerySet API,更加ORM友好
- 性能更好
- 类型安全,不易引入SQL注入
- 支持更复杂的聚合和计算
- 可以使用各种模型函数和表达式
- 更加Pythonic和Django推荐的方式
实际使用建议:
- 尽量使用
annotate()
,以获得更好的性能和安全性。 - 对于复杂的自定义查询,可以配合
Func
、F()
、Value()
等函数 - 避免使用
extra()
,除非有特殊兼容性需求
示例对比:
# extra() 方式(不推荐)
queryset = Model.objects.extra(select={'custom_field': 'some_sql_expression'})
# annotate() 方式(推荐)
from django.db.models import F, Value, CharField
queryset = Model.objects.annotate(
custom_field=F('some_field') + Value('suffix', output_field=CharField())
)