一.性能方面
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'}]
使用connection.queries可以来查看所有的sql查询语句及查询时间,仅仅当系统的 DEBUG 参数设为 True,上述命令才可生效,而且是按照查询的顺序排列的一个数组。因为这个记录是按照时间顺序排列的,所以 connection.queries[-1] 总能查询到最新的一条记录。
2.多数据库操作
如果系统用的是多个数据库,那么可以通过 connections['db_alias'].queries 来操作,比如我们使用的数据库的 alias 为 user:
>>> from django.db import connections >>> connections['user'].queries
如果想清空之前的记录,可以调用 reset_queries() 函数:
from django.db import reset_queries reset_queries()
3. explain
我们也可以使用 explain() 函数来查看一条 QuerySet 的执行计划,包括索引以及联表查询的的一些信息,这个操作和mysql数据库中操作是一样的。
>>> 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 mse
二、使用标准的数据库优化技术
数据库优化技术指的是在查询操作中 SQL 底层本身的优化,不涉及 Django 的查询操作
比如使用 索引 index,可以使用 Meta.indexes 或者字段里的 Field.db_index 来添加索引
如果频繁的使用到 filter()、exclude()、order_by() 等操作,建议为其中查询的字段添加索引,因为索引能帮助加快查询。
三、理解 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) 数据什么时候被加载:
迭代、使用步长分片、使用len()函数获取长度以及使用list()将QuerySet 转化成列表的时候数据才会被加载。
3) 数据是怎么被保存在内存中的:
当我们创建一个 QuerySet 的之后,并且数据第一次被加载,对数据库的查询操作就发生了。
然后 Django 会保存 QuerySet 查询的结果,并且在之后对这个 QuerySet 的操作中会重复使用,不会再去查询数据库。
当然,如果理解了这个原理之后,用得好就OK,否则会对数据库进行多次查询,造成性能的浪费,比如下面的操作:
>>> print([e.headline for e in Entry.objects.all()]) >>> print([e.pub_date for e in Entry.objects.all()])
上面的代码,同样一个查询操作,系统会查询两遍数据库,而且对于数据来说,两次的间隔期之间,Entry 表可能的某些数据库可能会增加或者被删除造成数据的不一致。
为了避免此类问题,我们可以这样复用这个 QuerySet :
>>> queryset = Entry.objects.all() >>> print([p.headline for p in queryset]) # 查询数据库 >>> print([p.pub_date for p in queryset]) # 从缓存中直接使用,不会再次查询数据库
上述中只查询了数据库1次。
使用数组的切片或者根据索引(即下标)不会缓存数据
举个例子,比如下面的操作,在缓存整个 QuerySet 数据前,查询一个 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]) # 使用缓存
上述中[entry for entry in queryset] 将queryset加载到了内存中。
如果一个 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、操作尽量在数据库中完成而不是在内存中