
原文链接:Django笔记二十八之数据库查询优化汇总
以下是本篇笔记目录:
- 性能方面
- 使用标准的数据库优化技术
- 理解 QuerySet
- 操作尽量在数据库中完成而不是在内存中
- 使用唯一索引来查询单个对象
- 如果知道需要什么数据,那么就立刻查出来
- 不要查询你不需要的数据
- 使用批量的方法
1、性能方面
1. connection.queries
>>> from django.db import connection
>>> connection.queries
[{'sql': 'SELECT polls_polls.id, polls_polls.question, polls_polls.pub_date FROM polls_polls',
'time': '0.002'}]
仅仅当系统的 DEBUG 参数设为 True,上述命令才可生效,而且是按照查询的顺序排列的一个数组
sql 为查询转化的查询语句
time 为查询过程中的耗时
多数据库操作
>>> from django.db import connections
>>> connections['user'].queries
如果想清空之前的记录,可以调用 reset_queries( 函数:
from django.db import reset_queries
reset_queries(
2. explain
我们也可以使用 explain( 函数来查看一条 QuerySet 的执行计划,包括索引以及联表查询的的一些信息
>>> print(Blog.objects.filter(title='My Blog'.explain(
Seq Scan on blog (cost=0.00..35.50 rows=10 width=12
Filter: (title = 'My Blog'::bpchar
也可以加一些参数来查看更详细的信息:
>>> print(Blog.objects.filter(title='My Blog'.explain(verbose=True, analyze=True
Seq Scan on public.blog (cost=0.00..35.50 rows=10 width=12 (actual time=0.004..0.004 rows=10 loops=1
Output: id, title
Filter: (blog.title = 'My Blog'::bpchar
Planning time: 0.064 ms
Execution time: 0.058 ms
之前在使用 Django 的过程中还使用到一个叫 silk 的工具,它可以用来分析一个接口各个步骤的耗时,有兴趣的可以了解一下。
2、使用标准的数据库优化技术
比如使用 索引 index,可以使用 Meta.indexes 或者字段里的 Field.db_index 来添加索引
3、理解 QuerySet
1. 理解 QuerySet 获取数据的过程
1 QuerySet 的懒加载
一个查询的创建并不会访问数据库,直到获取这条查询语句的具体数据的时候,系统才会去访问数据库:
>>> q = Entry.objects.filter(headline__startswith="What" # 不访问数据库
>>> q = q.filter(pub_date__lte=datetime.date.today( # 不访问数据库
>>> q = q.exclude(body_text__icontains="food" # 不访问数据库
>>> print(q # 访问数据库
比如上面四条语句,只有最后一步,系统才会去查询数据库。
2 数据什么时候被加载
这几点情况在我们的第九篇笔记中都有详细的描述。
3 数据是怎么被保存在内存中的
当我们创建一个 QuerySet 的之后,并且数据第一次被加载,对数据库的查询操作就发生了。
当然,如果理解了这个原理之后,用得好就OK,否则会对数据库进行多次查询,造成性能的浪费,比如下面的操作:
>>> print([e.headline for e in Entry.objects.all(]
>>> print([e.pub_date for e in Entry.objects.all(]
上面的代码,同样一个查询操作,系统会查询两遍数据库,而且对于数据来说,两次的间隔期之间,Entry 表可能的某些数据库可能会增加或者被删除造成数据的不一致。
>>> queryset = Entry.objects.all(
>>> print([p.headline for p in queryset] # 查询数据库
>>> print([p.pub_date for p in queryset] # 从缓存中直接使用,不会再次查询数据库
这样的操作系统就只执行了一遍查询操作。
使用数组的切片或者根据索引(即下标不会缓存数据
有的话,则直接从缓存中获取数据,没有的话,后续也不会将这部分数据缓存到系统中。
>>> queryset = Entry.objects.all(
>>> print(queryset[5] # 查询数据库
>>> print(queryset[5] # 再次查询数据库
而在下面的操作中,整个 QuerySet 都被提前获取了,那么根据索引的下标获取数据,则能够从缓存中直接获取数据:
>>> queryset = Entry.objects.all(
>>> [entry for entry in queryset] # 查询数据库
>>> print(queryset[5] # 使用缓存
>>> print(queryset[5] # 使用缓存
如果一个 QuerySet 已经缓存到内存中,那么下面的操作将不会再次查询数据库:
>>> [entry for entry in queryset]
>>> bool(queryset
>>> entry in queryset
>>> list(queryset
2. 理解 QuerySet 的缓存
除了 QuerySet 的缓存,单个 model 的 object 也有缓存的操作。
比如下面外键字段的获取,blog 是 Entry 的一个外键字段:
>>> entry = Entry.objects.get(id=1
>>> entry.blog # Blog 的实例被查询数据库获得
>>> entry.blog # 第二次获取,使用缓存信息,不会查询数据库
而多对多关系的获取每次都会被重新去数据库获取数据:
>>> entry = Entry.objects.get(id=1
>>> entry.authors.all( # 查询数据库
>>> entry.authors.all( # 再次查询数据库
当然,以上的操作,我们都可以通过 select_related( 和 prefetch_related( 的方式来减少数据库的访问,这个的用法在前面的笔记中有介绍。
4、操作尽量在数据库中完成而不是在内存中
- 在大多数查询中,使用 filter( 和 exclude( 在数据库中做过滤,而不是在获取所有数据之后在 Python 里的 for 循环里筛选数据
- 在同一个 model 的操作中,如果有涉及到其他字段的操作,可以用到 F 表达式
- 使用 annotate 函数在数据库中做聚合(aggregate)的操作
如果某些查询比较复杂,可以使用原生的 SQL 语句,这个操作也在前面有过一篇完整的笔记介绍过
5、使用唯一索引来查询单个对象
一个是基于数据库索引,查询会更快,
所以使用下面的 id 进行匹配 会比 headline 字段匹配快得多,因为 id 字段在数据库中有索引且是唯一的:
entry = Entry.objects.get(id=10
entry = Entry.objects.get(headline="News Item Title"
而下面的操作可能会更慢:
entry = Entry.objects.get(headline__startswith="News"
首先,headline 字段上没有索引,会导致数据库获取速度慢
6、如果知道需要什么数据,那么就立刻查出来
能一次性查询所有需要的相关的数据的话,就一次性查询出来,不要在循环中做多次查询,因为那样会多次访问数据库
7、不要查询你不需要的数据
1. 使用 values( 和 values_list( 函数
如果需求仅仅是需要某几个字段的数据,可以用到的数据结构为 dict 或者 list,可以直接使用这两个函数来获取数据
2. 使用 defer( 和 only(
3. 使用 count(
如果想要获取总数,使用 count( 方法,而不是使用 len( 来操作,如果数据有一万条,len( 操作会导致这一万条数据都加载到内存里,然后计数。
4. 使用 exists(
5. 使用 update( 和 delete(
能够批量更新和删除的操作就使用批量的方法,挨个去加载数据,更新数据,然后保存是不推荐的
6. 直接使用外键的值
比如推荐:
entry.blog_id
而不是:
entry.blog.id
7. 如果不需要排序的结果,就不要order_by(
每一个字段的排序都是数据库的操作需要额外消耗性能的,所以如果不需要的话,尽量不要排序
为数据库添加索引,可以帮助提高排序的性能
8、使用批量的方法
1. 批量创建
2. 批量更新
bulk_update 方法也优于挨个数据在 for 循环中去 save(
3. 批量 insert
my_band.members.add(me, my_friend
要优于:
my_band.members.add(me
my_band.members.add(my_friend
4. 批量 remove
当去除 ManyToMany 中的数据的时候,也是能一次性操作就一次性操作:
my_band.members.remove(me, my_friend
要好于:
my_band.members.remove(me
my_band.members.remove(my_friend
如果想获取更多后端相关文章,可扫码关注阅读: