一、QuerySet

可切片

使用Python 的切片语法来限制查询集记录的数目 。它等同于SQL 的LIMITOFFSET 子句。

  1. >>> Entry.objects.all()[:5] # (LIMIT 5)
  2. >>> Entry.objects.all()[5:10] # (OFFSET 5 LIMIT 5)

不支持负的索引(例如Entry.objects.all()[-1])。通常,查询集 的切片返回一个新的查询集 —— 它不会执行查询。

可迭代

  1. articleList=models.Article.objects.all()
  2. for article in articleList:
  3. print(article.title)

惰性查询

查询集 是惰性执行的 —— 创建查询集不会带来任何数据库的访问。你可以将过滤器保持一整天,直到查询集 需要求值时,Django 才会真正运行这个查询。

  1. queryResult=models.Article.objects.all() # not hits database
  2. print(queryResult) # hits database
  3. for article in queryResult:
  4. print(article.title) # hits database

一般来说,只有在“请求”查询集 的结果时才会到数据库中去获取它们。当你确实需要结果时,查询集 通过访问数据库来求值

缓存机制

每个查询集都包含一个缓存来最小化对数据库的访问。理解它是如何工作的将让你编写最高效的代码。

在一个新创建的查询集中,缓存为空。首次对查询集进行求值 —— 同时发生数据库查询 ——Django 将保存查询的结果到查询集的缓存中并返回明确请求的结果(例如,如果正在迭代查询集,则返回下一个结果)。接下来对该查询集 的求值将重用缓存的结果。

请牢记这个缓存行为,因为对查询集使用不当的话,它会坑你的。例如,下面的语句创建两个查询集,对它们求值,然后扔掉它们:

  1. print([a.title for a in models.Article.objects.all()])
  2. print([a.create_time for a in models.Article.objects.all()])

这意味着相同的数据库查询将执行两次,显然倍增了你的数据库负载。同时,还有可能两个结果列表并不包含相同的数据库记录,因为在两次请求期间有可能有Article被添加进来或删除掉。为了避免这个问题,只需保存查询集并重新使用它:

  1. queryResult=models.Article.objects.all()
  2. print([a.title for a in queryResult])
  3. print([a.create_time for a in queryResult])

何时查询集不会被缓存?

查询集不会永远缓存它们的结果。当只对查询集的部分进行求值时会检查缓存, 如果这个部分不在缓存中,那么接下来查询返回的记录都将不会被缓存。所以,这意味着使用切片或索引来限制查询集将不会填充缓存。

例如,重复获取查询集对象中一个特定的索引将每次都查询数据库:

  1. >>> queryset = Entry.objects.all()
  2. >>> print queryset[5] # Queries the database
  3. >>> print queryset[5] # Queries the database again

然而,如果已经对全部查询集求值过,则将检查缓存:

  1. >>> queryset = Entry.objects.all()
  2. >>> [entry for entry in queryset] # Queries the database
  3. >>> print queryset[5] # Uses cache
  4. >>> print queryset[5] # Uses cache

下面是一些其它例子,它们会使得全部的查询集被求值并填充到缓存中:

  1. >>> [entry for entry in queryset]
  2. >>> bool(queryset)
  3. >>> entry in queryset
  4. >>> list(queryset)

注:简单地打印查询集不会填充缓存。

  1. queryResult=models.Article.objects.all()
  2. print(queryResult) # hits database
  3. print(queryResult) # hits database

二、exists()与iterator()方法

exists

简单的使用if语句进行判断也会完全执行整个queryset并且把数据放入cache,虽然你并不需要这些 数据!为了避免这个,可以用exists()方法来检查是否有数据:

  1. if queryResult.exists():
  2. #SELECT (1) AS "a" FROM "blog_article" LIMIT 1; args=()
  3. print("exists...")

iterator

当queryset非常巨大时,cache会成为问题。

处理成千上万的记录时,将它们一次装入内存是很浪费的。更糟糕的是,巨大的queryset可能会锁住系统 进程,让你的程序濒临崩溃。要避免在遍历数据的同时产生queryset cache,可以使用iterator()方法 来获取数据,处理完数据就将其丢弃。

  1. objs = Book.objects.all().iterator()
  2. # iterator()可以一次只从数据库获取少量数据,这样可以节省内存
  3. for obj in objs:
  4. print(obj.title)
  5. #BUT,再次遍历没有打印,因为迭代器已经在上一次遍历(next)到最后一次了,没得遍历了
  6. for obj in objs:
  7. print(obj.title)

当然,使用iterator()方法来防止生成cache,意味着遍历同一个queryset时会重复执行查询。所以使 #用iterator()的时候要当心,确保你的代码在操作一个大的queryset时没有重复执行查询。

总结:

queryset的cache是用于减少程序对数据库的查询,在通常的使用下会保证只有在需要的时候才会查询数据库。 使用exists()和iterator()方法可以优化程序对内存的使用。不过,由于它们并不会生成queryset cache,可能 会造成额外的数据库查询。

三、查询优化

select_related(基于连表查询)

  1. 简单使用

    对于一对一字段(OneToOneField)和外键字段(ForeignKey),可以使用select_related 来对QuerySet进行优化。

    select_related 返回一个QuerySet,当执行它的查询时它沿着外键关系查询关联的对象的数据。它会生成一个复杂的查询并引起性能的损耗,但是在以后使用外键关系时将不需要数据库查询。

    简单说,在对QuerySet使用select_related()函数后,Django会获取相应外键对应的对象,从而在之后需要的时候不必再查询数据库了。

    下面的例子解释了普通查询和select_related() 查询的区别。

    查询id=2的文章的分类名称,下面是一个标准的查询:

    1. # Hits the database.
    2. article = models.Article.objects.get(nid=2)
    3. # Hits the database again to get the related Blog object.
    4. print(article.category.title)

    对应SQL:

    1. SELECT
    2. "blog_article"."nid",
    3. "blog_article"."title",
    4. "blog_article"."desc",
    5. "blog_article"."read_count",
    6. "blog_article"."comment_count",
    7. "blog_article"."up_count",
    8. "blog_article"."down_count",
    9. "blog_article"."category_id",
    10. "blog_article"."create_time",
    11. "blog_article"."blog_id",
    12. "blog_article"."article_type_id"
    13. FROM "blog_article"
    14. WHERE "blog_article"."nid" = 2; args=(2,)
    15. SELECT
    16. "blog_category"."nid",
    17. "blog_category"."title",
    18. "blog_category"."blog_id"
    19. FROM "blog_category"
    20. WHERE "blog_category"."nid" = 4; args=(4,)

    如果我们使用select_related()函数:

    1. articleList=models.Article.objects.select_related("category").all()
    2. for article_obj in articleList:
    3. # Doesn't hit the database, because article_obj.category
    4. # has been prepopulated in the previous query.
    5. print(article_obj.category.title)

    对应SQL:

    1. SELECT
    2. "blog_article"."nid",
    3. "blog_article"."title",
    4. "blog_article"."desc",
    5. "blog_article"."read_count",
    6. "blog_article"."comment_count",
    7. "blog_article"."up_count",
    8. "blog_article"."down_count",
    9. "blog_article"."category_id",
    10. "blog_article"."create_time",
    11. "blog_article"."blog_id",
    12. "blog_article"."article_type_id",
    13. "blog_category"."nid",
    14. "blog_category"."title",
    15. "blog_category"."blog_id"
    16. FROM "blog_article"
    17. LEFT OUTER JOIN "blog_category" ON ("blog_article"."category_id" = "blog_category"."nid");
  2. 多外键查询

    这是针对category的外键查询,如果是另外一个外键呢?让我们一起看下:

    1. article=models.Article.objects.select_related("category").get(nid=1)
    2. print(article.articledetail)

    观察logging结果,发现依然需要查询两次,所以需要改为:

    1. article=models.Article.objects.select_related("category","articledetail").get(nid=1)
    2. print(article.articledetail)

    或者:

    1. article=models.Article.objects
    2.              .select_related("category")
    3.              .select_related("articledetail")
    4.              .get(nid=1) # django 1.7 支持链式操作
    5. print(article.articledetail)

    对应SQL:

    1. SELECT
    2. "blog_article"."nid",
    3. "blog_article"."title",
    4. ......
    5. "blog_category"."nid",
    6. "blog_category"."title",
    7. "blog_category"."blog_id",
    8. "blog_articledetail"."nid",
    9. "blog_articledetail"."content",
    10. "blog_articledetail"."article_id"
    11. FROM "blog_article"
    12. LEFT OUTER JOIN "blog_category" ON ("blog_article"."category_id" = "blog_category"."nid")
    13. LEFT OUTER JOIN "blog_articledetail" ON ("blog_article"."nid" = "blog_articledetail"."article_id")
    14. WHERE "blog_article"."nid" = 1; args=(1,)
  3. 深层查询

    1. # 查询id=1的文章的用户姓名
    2. article=models.Article.objects.select_related("blog").get(nid=1)
    3. print(article.blog.user.username)

    这是因为第一次查询没有query到userInfo表,所以,修改如下:

    1. article=models.Article.objects.select_related("blog__user").get(nid=1)
    2. print(article.blog.user.username)

    对应SQL:

    1. SELECT
    2. "blog_article"."nid", "blog_article"."title",
    3. ......
    4. "blog_blog"."nid", "blog_blog"."title",
    5. ......
    6. "blog_userinfo"."password", "blog_userinfo"."last_login",
    7. ......
    8. FROM "blog_article"
    9. INNER JOIN "blog_blog" ON ("blog_article"."blog_id" = "blog_blog"."nid")
    10. INNER JOIN "blog_userinfo" ON ("blog_blog"."user_id" = "blog_userinfo"."nid")
    11. WHERE "blog_article"."nid" = 1;
  4. 总结

    • select_related主要针一对一和多对一关系进行优化。
    • select_related使用SQL的JOIN语句进行优化,通过减少SQL查询的次数来进行优化、提高性能。
    • 可以通过可变长参数指定需要select_related的字段名。也可以通过使用双下划线“__”连接字段名来实现指定的递归查询。
    • 没有指定的字段不会缓存,没有指定的深度不会缓存,如果要访问的话Django会再次进行SQL查询。
    • 也可以通过depth参数指定递归的深度,Django会自动缓存指定深度内所有的字段。如果要访问指定深度外的字段,Django会再次进行SQL查询。
    • 也接受无参数的调用,Django会尽可能深的递归查询所有的字段。但注意有Django递归的限制和性能的浪费。
    • Django >= 1.7,链式调用的select_related相当于使用可变长参数。Django < 1.7,链式调用会导致前边的select_related失效,只保留最后一个。

prefetch_related(基于子查询)

对于多对多字段(ManyToManyField)和一对多字段,可以使用prefetch_related()来进行优化。

prefetch_related()和select_related()的设计目的很相似,都是为了减少SQL查询的数量,但是实现的方式不一样。后者是通过JOIN语句,在SQL查询内解决问题。但是对于多对多关系,使用SQL语句解决就显得有些不太明智,因为JOIN得到的表将会很长,会导致SQL语句运行时间的增加和内存占用的增加。若有n个对象,每个对象的多对多字段对应Mi条,就会生成Σ(n)Mi 行的结果表。

prefetch_related()的解决方法是,分别查询每个表,然后用Python处理他们之间的关系。

  1. # 查询所有文章关联的所有标签
  2. article_obj=models.Article.objects.all()
  3. for i in article_obj:
  4. print(i.tags.all()) #4篇文章: hits database 5

改为prefetch_related:

  1. # 查询所有文章关联的所有标签
  2. article_obj=models.Article.objects.prefetch_related("tags").all()
  3. for i in article_obj:
  4. print(i.tags.all()) #4篇文章: hits database 2

对应SQL:

  1. SELECT "blog_article"."nid",
  2. "blog_article"."title",
  3. ......
  4. FROM "blog_article";
  5. SELECT
  6. ("blog_article2tag"."article_id") AS "_prefetch_related_val_article_id",
  7. "blog_tag"."nid",
  8. "blog_tag"."title",
  9. "blog_tag"."blog_id"
  10. FROM "blog_tag"
  11. INNER JOIN "blog_article2tag" ON ("blog_tag"."nid" = "blog_article2tag"."tag_id")
  12. WHERE "blog_article2tag"."article_id" IN (1, 2, 3, 4);

四、extra

  1. extra(select=None, where=None, params=None,
  2. tables=None, order_by=None, select_params=None)

有些情况下,Django的查询语法难以简单的表达复杂的 WHERE 子句,对于这种情况, Django 提供了 extra() QuerySet修改机制 — 它能在 QuerySet生成的SQL从句中注入新子句

extra可以指定一个或多个 参数,例如 select, where or tables. 这些参数都不是必须的,但是你至少要使用一个!要注意这些额外的方式对不同的数据库引擎可能存在移植性问题.(因为你在显式的书写SQL语句),除非万不得已,尽量避免这样做

参数之select

The select 参数可以让你在 SELECT 从句中添加其他字段信息,它应该是一个字典,存放着属性名到 SQL 从句的映射。

  1. queryResult=models.Article.objects.extra(select={'is_recent': "create_time > '2017-09-05'"})

结果集中每个 Entry 对象都有一个额外的属性is_recent, 它是一个布尔值,表示 Article对象的create_time 是否晚于2017-09-05.

练习:

  1. # in sqlite:
  2. article_obj=models.Article.objects
  3.               .filter(nid=1)
  4.               .extra(select={"standard_time":"strftime('%%Y-%%m-%%d',create_time)"})
  5.               .values("standard_time","nid","title")
  6. print(article_obj)
  7. # <QuerySet [{'title': 'MongoDb 入门教程', 'standard_time': '2017-09-03', 'nid': 1}]>

参数之where / tables

您可以使用where定义显式SQL WHERE子句 - 也许执行非显式连接。您可以使用tables手动将表添加到SQL FROM子句。

wheretables都接受字符串列表。所有where参数均为“与”任何其他搜索条件。

举例来讲:

  1. queryResult=models.Article.objects.extra(where=['nid in (1,3) OR title like "py%" ','nid>2'])

五、整体插入

创建对象时,尽可能使用bulk_create()来减少SQL查询的数量。例如:

  1. Entry.objects.bulk_create([
  2. Entry(headline="Python 3.0 Released"),
  3. Entry(headline="Python 3.1 Planned")
  4. ])

...更优于:

  1. Entry.objects.create(headline="Python 3.0 Released")
  2. Entry.objects.create(headline="Python 3.1 Planned")

注意该方法有很多注意事项,所以确保它适用于你的情况。

这也可以用在ManyToManyFields中,所以:

  1. my_band.members.add(me, my_friend)

...更优于:

  1. my_band.members.add(me)
  2. my_band.members.add(my_friend)

...其中Bands和Artists具有多对多关联。

六、中介模型

处理类似搭配 pizza 和 topping 这样简单的多对多关系时,使用标准的ManyToManyField 就可以了。但是,有时你可能需要关联数据到两个模型之间的关系上。

例如,有这样一个应用,它记录音乐家所属的音乐小组。我们可以用一个ManyToManyField 表示小组和成员之间的多对多关系。但是,有时你可能想知道更多成员关系的细节,比如成员是何时加入小组的。

对于这些情况,Django 允许你指定一个中介模型来定义多对多关系。 你可以将其他字段放在中介模型里面。源模型的ManyToManyField 字段将使用through 参数指向中介模型。对于上面的音乐小组的例子,代码如下:

  1. from django.db import models
  2. class Person(models.Model):
  3. name = models.CharField(max_length=128)
  4. def __str__(self): # __unicode__ on Python 2
  5. return self.name
  6. class Group(models.Model):
  7. name = models.CharField(max_length=128)
  8. members = models.ManyToManyField(Person, through='Membership')
  9. def __str__(self): # __unicode__ on Python 2
  10. return self.name
  11. class Membership(models.Model):
  12. person = models.ForeignKey(Person)
  13. group = models.ForeignKey(Group)
  14. date_joined = models.DateField()
  15. invite_reason = models.CharField(max_length=64)

既然你已经设置好ManyToManyField 来使用中介模型(在这个例子中就是Membership),接下来你要开始创建多对多关系。你要做的就是创建中介模型的实例:

  1. >>> ringo = Person.objects.create(name="Ringo Starr")
  2. >>> paul = Person.objects.create(name="Paul McCartney")
  3. >>> beatles = Group.objects.create(name="The Beatles")
  4. >>> m1 = Membership(person=ringo, group=beatles,
  5. ... date_joined=date(1962, 8, 16),
  6. ... invite_reason="Needed a new drummer.")
  7. >>> m1.save()
  8. >>> beatles.members.all()
  9. [<Person: Ringo Starr>]
  10. >>> ringo.group_set.all()
  11. [<Group: The Beatles>]
  12. >>> m2 = Membership.objects.create(person=paul, group=beatles,
  13. ... date_joined=date(1960, 8, 1),
  14. ... invite_reason="Wanted to form a band.")
  15. >>> beatles.members.all()
  16. [<Person: Ringo Starr>, <Person: Paul McCartney>]

与普通的多对多字段不同,你不能使用addcreate和赋值语句(比如,beatles.members = [...])来创建关系:

  1. # THIS WILL NOT WORK
  2. >>> beatles.members.add(john)
  3. # NEITHER WILL THIS
  4. >>> beatles.members.create(name``=``"George Harrison"``)
  5. # AND NEITHER WILL THIS
  6. >>> beatles.members ``=` `[john, paul, ringo, george]

为什么不能这样做? 这是因为你不能只创建 PersonGroup之间的关联关系,你还要指定 Membership模型中所需要的所有信息;而简单的addcreate 和赋值语句是做不到这一点的。所以它们不能在使用中介模型的多对多关系中使用。此时,唯一的办法就是创建中介模型的实例。

remove()方法被禁用也是出于同样的原因。但是clear() 方法却是可用的。它可以清空某个实例所有的多对多关系:

  1. >>> # Beatles have broken up
  2. >>> beatles.members.clear()
  3. >>> # Note that this deletes the intermediate model instances
  4. >>> Membership.objects.all()
  5. []

Django ORM 高性能查询优化的更多相关文章

  1. Django orm进阶查询(聚合、分组、F查询、Q查询)、常见字段、查询优化及事务操作

    Django orm进阶查询(聚合.分组.F查询.Q查询).常见字段.查询优化及事务操作 聚合查询 记住用到关键字aggregate然后还有几个常用的聚合函数就好了 from django.db.mo ...

  2. django orm 基本

    1 modle基本数据类型 class Test(models.Model): """测试学习用""" Auto = models.Auto ...

  3. django orm查询和后端缓存的使用

    django orm 查询 1 字段后(db_column='age') (null=True)#表示数据库里面的该字段数据可以为空 (blank=True)#表示前端表单提交的时候可以为空 (db_ ...

  4. SQL高性能查询优化语句(总结)

    SQL 高性能查询优化语句,一些经验总结 1.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:select id from t where ...

  5. django orm总结[转载]

    django orm总结[转载] 转载地址: http://www.cnblogs.com/linjiqin/archive/2014/07/01/3817954.html 目录1.1.1 生成查询1 ...

  6. Django ORM - 001 - 外键表查询主表信息

    开始用Django做web开发,我想大家都会遇到同样的问题,那就是如何高效快速的查询需要的数据,MVC都很简单,但是ORM折腾起来就有些费时间,我准备好好研究下Django ORM,所以会有一个系列的 ...

  7. Django ORM 中的批量操作

    Django ORM 中的批量操作 在Hibenate中,通过批量提交SQL操作,部分地实现了数据库的批量操作.但在Django的ORM中的批量操作却要完美得多,真是一个惊喜. 数据模型定义 首先,定 ...

  8. Django ORM 查询管理器

    Django ORM 查询管理器 ORM 查询管理器 对于 ORM 定义: 对象关系映射, Object Relational Mapping, ORM, 是一种程序设计技术,用于实现面向对象编程语言 ...

  9. Django ORM模型的一点体会

    作者:Vamei 出处:http://www.cnblogs.com/vamei 严禁转载. 使用Python的Django模型的话,一般都会用它自带的ORM(Object-relational ma ...

随机推荐

  1. 2019 ICPC 沈阳网络赛 J. Ghh Matin

    Problem Similar to the strange ability of Martin (the hero of Martin Martin), Ghh will random occurr ...

  2. ansible handlers

    示例:安装nginx --- - hosts: hadoop #指定主机组 remote_user: root #远程执行命令的用户 gather_facts: no #是否获取远程主机的信息 tas ...

  3. mysql 导入导出表结构和表数据

    mysqldump -u用户名 -p密码 -d 数据库名 表名 > 脚本名; 导出整个数据库结构和数据mysqldump -h localhost -uroot -p123456 databas ...

  4. Spring boot 集成Solr

    首先安装Solr 集成 ikanalyzer ,可以参考 https://www.cnblogs.com/lick468/p/10867492.html https://www.cnblogs.com ...

  5. boost 介绍

    简介: Boost库是一个可移植.提供源代码的C++库,作为标准库的后备,是C++标准化进程的开发引擎之一. Boost库由C++标准委员会库工作组成员发起,其中有些内容有望成为下一代C++标准库内容 ...

  6. (四)OpenCV-Python学习—形态学处理

    通过阈值化分割可以得到二值图,但往往会出现图像中物体形态不完整,变的残缺,可以通过形态学处理,使其变得丰满,或者去除掉多余的像素.常用的形态学处理算法包括:腐蚀,膨胀,开运算,闭运算,形态学梯度,顶帽 ...

  7. Java网络编程之Netty

    一.Netty概述 Netty 是由JBOSS 提供的一个java 开源框架.Netty 提供异步的.事件驱动的网络应用程序框架和工具,用以快速开发高性能.高可靠性的网络服务器和客户端程序. 也就是说 ...

  8. 【转】nodejs接收前端formData数据

    很多时候需要利用formdata数据格式进行前后端交互. 前端代码可以是如下所示: <!DOCTYPE html> <html lang="en"> < ...

  9. Android插件化技术——原理篇

    <Android插件化技术——原理篇>     转载:https://mp.weixin.qq.com/s/Uwr6Rimc7Gpnq4wMFZSAag?utm_source=androi ...

  10. Ionic4.x Theming(主题) 增加内置主题 颜色 修改内置组件默认样式 修改底部 Tabs 背景颜色以及按钮颜色

    1.Ionic4.x Theming(主题) Ionic4.x 修改主题颜色的话需要在 src/theme/variables.scss 文件中修改. https://ionicframework.c ...