ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
### 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自己去一个个实验一下**