### QuerySet的细节
**QuerySet算是Django ORM中的核心工具了,尤其对于Django为后台的网站中,大部分的性能问题来自数据库,而如何高效,节约的使用QuerySet就是Django中的一个非常核心和关键的问题,这里介绍一些QuerySet的特性和高效的使用技巧**
QuerySet在什么情况下会被求值:
1. 迭代
2. 切片
3. pickling
4. repr()
5. len()
6. list()
7. bool()
QuerySet是有缓存机制的,在QuerySet已经被使用过一次查询之后,QuerySet会把结果保存起来供以后使用
~~~py
>>> queryset = Entry.objects.all()
>>> print([p.headline for p in queryset]) # Evaluate the query set.
>>> print([p.pub_date for p in queryset]) # Re-use the cache from the evaluation.
~~~
<br>
在QuerySet没有hit数据库之前如果直接利用下表取数据,QuerySet并不会缓存数据
~~~py
>>> queryset = Entry.objects.all()
>>> print(queryset[5]) # Queries the database
>>> print(queryset[5]) # Queries the database again
~~~
如果已经hit过了还是会从缓存取数据
~~~py
>>> queryset = Entry.objects.all()
>>> [entry for entry in queryset] # Queries the database
>>> print(queryset[5]) # Uses cache
>>> print(queryset[5]) # Uses cache
~~~
**PS:update,create方法支持批量修改,且update,create是直接生效的**
#### select_related的作用
select_related是对查询表中的外键进行查询且cache的过程,因为在Django的查询语句中,如果没有使用select_related语句来查找外键,每一次对外键的查询都将击中一次数据库,以例子来说,下面的语句如果没有select_related就会每循环一次查询一次数据库
~~~
from django.utils import timezone
# Find all the blogs with entries scheduled to be published in the future.
blogs = set()
for e in Entry.objects.filter(pub_date__gt=timezone.now()).select_related('blog'):
# 没有select_related(),下面的语句将为每次循环迭代生成一个数据库查询
# 以获得每个entry关联的blog。
blogs.add(e.blog)
~~~
同时select_related在查询语句中的顺序不重要,何处皆可,下面两条语句效果一样
~~~
Entry.objects.filter(pub_date__gt=timezone.now()).select_related('blog')
Entry.objects.select_related('blog').filter(pub_date__gt=timezone.now())
~~~
select_related是递归查询外键的,也就是说你可以指定外键的外键这样往复下去直到终点,且这个查询路上的所有外键的数据都已经被记录下来,再次通过属性访问时是不需要击中数据库的
#### prefetch_related的作用
prefetch和select的最终目的是一样的,都是为了预存储关联外键的数据,而简单来说prefetch和select的不同就在于select是存储一对一和多对一关系的,prefetch是存储多对多和一对多关系
**Hint:** 这里有一个很重要的问题需要明确,一旦涉及外键的关联查询,例如上面这两个方法,实际上都是从关联的下层往上层寻找的方法,而从上层往下层找的反向查询在Django里很难通过ORM做到,除了单个对象的entry_set这种意外,其他的都不是很好实现,所以有时候还是需要RawSQL来帮忙
#### 实际案例解析
今天在项目代码中就碰到了一个这样的情况,首先有三张表
1. OrionUsers
2. UserPositionHistories
3. PositionHistories
情境是,我们需要找到一个OrionUser里的对象满足它的user_id等于UserPositionHistories里的user_id,同时UserPositionHistories的这行数据的position_histories_id等于PositionHistories的id,实际上这种情况如果用纯粹的SQL来实现非常简单,两个JOIN ON加上WHERE就完事了,但是在Django的ORM中好像就很难有办法可以一行代码解决这个问题
#### 一些可以避免进行数据库查询的情况
QuerySet在声明时是不会进行数据库查询的,只有在特定情况下它里面的SQL语句才会被触发真正去到DB里面查找数据,这里有一些常见的情况我们并不需要去使用它里面的SQL语句
1. 当你只是需要确认一个QuerySet中是否含有数据时,你并不需要Python语法的判断,而是可以利用QuerySet的exists()函数去判断
~~~
# Don't waste a query if you are using the queryset
books = Book.objects.filter(..)
if books:
do_stuff_with_books(books)
# If you aren't using the queryset use exist
books = Book.objects.filter(..)
if books.exists():
do_some_stuff()
# But never
if Book.objects.filter(..):
do_some_stuff()
~~~
1. 同时Python的将QuerySet作为参数传入Python的内置函数中也会调用SQL
~~~
# Retrieve values as a dictionary
>>> Book.objects.values('title', 'author__name')
<QuerySet [{'author__name': u'Nikolai Gogol', 'title': u'The Overcoat'}, {'author__name': u'Leo Tolstoy', 'title': u'War and Peace'}]>
# Retrieve values as a tuple
>>> Book.objects.values_list('title', 'author__name')
<QuerySet [(u'The Overcoat', u'Nikolai Gogol'),
(u'War and Peace', u'Leo Tolstoy')]>
>>> Book.objects.values_list('title')
<QuerySet [(u'The Overcoat',), (u'War and Peace',)]>
# With one value, it is easier to flatten the list
>>> Book.objects.values_list('title', flat=True)
<QuerySet [u'The Overcoat', u'War and Peace']>
~~~
<br>
#### values和values_list函数
有时我们并不需要一个数据的所有列的值,这时可以用values和value_list函数取出特定列的值,但是返回的已经不是真正的QuerySet了,同时这两个方法的返回值还有一定区别
<br>
1. values函数返回的是ValueQuerySet,实际上是QuerySet的一个子类,基本上还是可以看成是一个QuerySet,只是迭代时数组中的元素已经是一个字典了
2. values_list返回的就是一个元素为元组的数组了,不过同样返回值称之为ValuesListQuerySet,每个元组存储着每一行对应字段的值
#### aggregate和annotate
aggregate相当于就是对整个表格调用聚合函数,举一个🌰
~~~py
>>> from django.db.models import Count
>>> pubs = Publisher.objects.aggregate(num_books=Count('book'))
>>> pubs
{'num_books': 27}
~~~
<br>
这里就是调用了Django里面的ORM封装函数Count统计总共有多少本书,当然常用的几个SQL的聚合函数都是可以用到的,例如COUNT, AVG, SUM, MAX, MIN
但如果我们需要利用特定群组的聚合呢?这时候就要利用annotate了
下面这一行代码
~~~py
msgS = MessageTab.objects.values_list('msg_status').annotate(Count('id'))
~~~
<br>
实际上就等同于SQL语句
~~~
SELECT `message_tab`.`msg_status`, COUNT(`message_tab`.`id`) AS `id__count`
FROM `message_tab`
GROUP BY `message_tab`.`msg_status`
ORDER BY NULL
~~~
<br>
实际上上面这个SQL语句解释的很清楚了,就是以values_list中的列为聚合列,同时统计GROUP BY情况下各组id的数量
#### defer和only
这两个函数的意义正好相反,一个是延缓加载指定字段,一个是只加载指定字段,可能在某种程度上他们有点容易和values还有values_list混淆,但是他们的区别就在于一个是延缓,一个是真的不要其他字段了,同时返回的类也不一样,同时注意一旦有only后缀的queryset,**其每被调用一次至少会执行一次SQL语句**
**Hint: 如果需要详细的知道QuerySet的哪些函数会调用SQL从数据库拿东西而哪些不会,可以去看QuerySet的实现源代码,QuerySet是一个继承自Python的Object的类,所以它的源代相对比较容易理解,因为不牵扯Django的其他类了**
**Hint2:理解ORM中的高级方法的最好方式就是python manage.py shell自己去一个个实验一下**
- Django基础
- 模型
- 外键
- Model Manager
- 过滤器函数
- 查询对象
- 字段的细节
- QuerySet的应用
- 视图
- Django类视图
- 权限控制
- Django进阶
- 中间件
- _meta组件
- 信号
- User模块
- prefetch_related和select_related的区别
- 较少被用到的查询对象
- Django的深层设计理念
- Declarative Syntax
- django的migration操作
- 较少用到的Queryset方法的一些坑
- Django配置
- Django环境配置变量
- Django源码阅读
- ORM
- QuerySet源码
- Query源码
- Q&F
- Model和Manager的详解
- Http请求响应
- HttpRequest
- 自建数据结构
- Django开发辅助工具
- Django-rest-framework
- Serializer
- 异步任务调度器Celery
- 数据库补充
- 定义
- 字段
- 事务
- 视图
- 函数
- 联结
- 窗口函数
- GROUPING运算符
- HAVING谓词
- django_bulk_update
- django_bulk_update源码分析
- 项目小功能开发
- Django的一些小细节