官方推荐的一些优化:
https://docs.djangoproject.com/zh-hans/3.2/topics/db/optimization/
优化关联查询
关于
select_related
和prefetch_related
可以参考:
https://www.cnblogs.com/kylin5201314/p/14738195.html
https://docs.djangoproject.com/zh-hans/3.2/ref/models/querysets/#select-related
外键连接使用select_related
,多对多连接使用prefetch_related
select_related
的工作方式是创建一个 SQL 连接,并在 SELECT 语句中包含相关对象的字段。出于这个原因,select_related 在同一个数据库查询中得到相关对象。然而,为了避免因跨越“many”关系进行连接而产生更大的结果集,select_related 仅限于单值关系——外键和一对一。
prefetch_related
对每个关系进行单独的查找,并在 Python 中进行“joining”。这使得它除了支持 select_related 的外键和一对一关系外,还可以预取多对多和多对一的对象,这是用 select_related 无法做到的。
优化prefetch_related
:
举例:输出每个用户当前激活的权限名称
from django.db.models import Prefetch
queryset = RBACPermission.objects.filter(active=True).only('name')
users = RBACUser.objects.only('rbac_permissions').prefetch_related(
Prefetch(
'rbac_permissions', # 关联字段
queryset=queryset, # 关联查询集
to_attr='active_permissions' # 保存到属性名称
)
)
for u in users:
print([p.name for p in u.active_permissions])
最终查询语句:
SELECT (`rbac_user_rbac_permissions`.`rbacuser_id`) AS `_prefetch_related_val_rbacuser_id`, `rbac_permission`.`id`, `rbac_permission`.`name` FROM `rbac_permission` INNER JOIN `rbac_user_rbac_permissions` ON (`rbac_permission`.`id` = `rbac_user_rbac_permissions`.`rbacpermission_id`) WHERE (`rbac_permission`.`active` AND `rbac_user_rbac_permissions`.`rbacuser_id` IN (SELECT `rbac_user`.`id` FROM `rbac_user` ORDER BY `rbac_user`.`id` DESC)) ORDER BY `rbac_permission`.`code` DESC;
使用count()统计数量
使用len()来统计会执行QuerySet,但是使用count()统计数量,是在数据库层面执行统计(
SELECT COUNT(*)
),效率更高。
count = len(User.objects.all()) # x
count = User.objects.all().count() # √
使用exists()判断是否存在
if queryResult.exists():
#SELECT (1) AS "a" FROM "blog_article" LIMIT 1; args=()
print("exists...")
使用iterator迭代数据减少内存占用
MySQL 不支持流式结果,因此 Python 数据库驱动将整个结果集加载到内存中。当数据库后端是mysql时,就算使用iterator也是会一次查询所有结果到内存,但是结果不会缓存到queryset中。
细节可以看官方文档:https://docs.djangoproject.com/zh-hans/3.2/ref/models/querysets/#iterator
objs = Book.objects.all().iterator()
for obj in objs:
print(obj.title)
# 迭代器已经循环到底,再次遍历已经没有对象可以循环,但是也不会触发异常
for obj in objs:
print(obj.title)
使用only或defer减少查询字段
q1 = User.objects.all().only('name') # 只查询某列,延迟查询其它列,defer用法相反,延迟查询某一列
print(q1.query)
# SELECT `user`.`id`, `user`.`name` FROM `user` ORDER BY `user`.`id` DESC
# 与values不同,only和defer只是一个延迟查询,延迟查询的字段通过额外的sql同样可以获取到(框架自动完成),而values不可以。
q2 = User.objects.all().values('name')
print(q2.query)
# SELECT `user`.`id`, `user`.`name` FROM `user` ORDER BY `user`.`id` DESC
# 查询语句是一样的,但是通过values的结果,不能查询其它字段
避免直接对queryset进行索引操作
u = User.objects.all()
for i in range(10):
print(u[i]) # 每次产生一条新的查询,因为queryset没有缓存
list(u) # 计算queryset,并把记录缓存到queryset
for i in range(10):
print(u[i]) # 不会有新的查询
数据库添加索引
主键默认会添加索引,查询最好使用主键字段。
指定字段添加索引:
https://docs.djangoproject.com/zh-hans/3.2/ref/models/fields/#django.db.models.Field.db_index
https://docs.djangoproject.com/zh-hans/3.2/ref/models/options/#django.db.models.Options.indexes
联合索引:
https://docs.djangoproject.com/zh-hans/3.2/ref/models/options/#index-together
批量操作
批量创建
Entry.objects.bulk_create([
Entry(headline='This is a test'),
Entry(headline='This is only a test'),
])
批量更新
entries = Entry.objects.bulk_create([
Entry(headline='This is a test'),
Entry(headline='This is only a test'),
])
entries[0].headline = 'This is not a test'
entries[1].headline = 'This is no longer a test'
Entry.objects.bulk_update(entries, ['headline'])
批量插入
多对多关系:
my_band.members.add(me, my_friend)
批量删除
多对多关系:
my_band.members.remove(me, my_friend)
普通查询集:
Entry.objects.all().delete()
调试SQL查询
查看代码执行的sql语句
from django.db import connections, reset_queries
reset_queries() # 重置查询
# 执行sql操作
some_sql_ops()
# 打印sql
for c in connections.all():
print(f"Queries per connection: Database: {c.settings_dict['NAME']} {c.queries}")
查看语句详情
print(User.objects.filter(id__gt=10).query)
查看语句执行分析
print(Audit.objects.filter(id__gt=10).explain(format='JSON'))
{
"query_block": {
"select_id": 1,
"ordering_operation": {
"using_filesort": false,
"table": {
"table_name": "audit",
"access_type": "range",
"possible_keys": [
"PRIMARY"
],
"key": "PRIMARY",
"used_key_parts": [
"id"
],
"key_length": "4",
"rows": 2476,
"filtered": 100,
"attached_condition": "(`DBNAME`.`audit`.`id` > 10)"
}
}
}
}
若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏