本篇笔记目录如下:

  1. select_related
  2. prefetch_related

在介绍 select_related 和 prefetch_related 这两个函数前,我们先来看一个例子。

对于,Entry 和 Blog 这两个 model,前面介绍过,Blog 是 Entry 的外键,如下:

class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField() class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
headline = models.CharField(max_length=255)
body_text = models.TextField()
pub_date = models.DateField()
mod_date = models.DateField()
authors = models.ManyToManyField(Author)
number_of_comments = models.IntegerField()
number_of_pingbacks = models.IntegerField()
rating = models.IntegerField()

比如我们需要获取 Entry 的前十条数据,然后打印出关联的 Blog 的 name 字段信息。

我们一般会如此操作:

for entry in Entry.objects.all()[:10]
if entry.blog:
print(entry.blog.name)
else:
print("没有关联 blog 数据")

但是这样会有一个问题,那就是,这个 for 循环的操作会查询数据十一次,一次查询 Entry 数据,十次是查询每个 entry_obj 关联的 blog 数据。

这个设计对于系统来说是不合理的,想一想如果我们查询的数据是一千条,一万条,无论是系统接口的等待时间,还是数据库的访问压力,都是不可接受的。

因此我们可以引入 外键 和 ManyToManyTo 的一种能够减少数据库的访问次数的方式:select_related,prefetch_related。

select_related

当我们在使用的时候,如果有需要获取的外键数据,比如 Entry 关联的 Blog 数据,则可以将其字段名作为参数传入,这样在获取数据的时候就可以一次性将所有关联的 Blog 数据也取出来,而不用单独再去查询一遍数据库。

如下,批量操作

for entry in Entry.objects.select_related("blog").all():
print(e.blog) # 这个操作不会额外再去查询数据库

当然也适用于单条数据

e = Entry.objects.get(id=5).select_related("blog")

为了验证 select_related() 确实会只查询一遍数据库,有两种方法:

一种是在数据库层面打印出来所有查询的 SQL语句,

另一种可以从侧面表示,那就是在系统层面打印出我们的查询条件转化的 SQL 语句。

比如:

Entry.objects.select_related("blog").all().query.__str__()

可以看到会输出一个 关联了 Blog 表的 inner join 的 SQL 语句。

SELECT `blog_entry`.`id`, `blog_entry`.`blog_id`, `blog_entry`.`headline`, `blog_entry`.`body_text`, `blog_entry`.`pub_date`, `blog_entry`.`mod_date`, `blog_entry`.`number_of_comments`, `blog_entry`.`number_of_pingbacks`, `blog_entry`.`rating`, `blog_blog`.`id`, `blog_blog`.`name`, `blog_blog`.`tagline` FROM `blog_entry` INNER JOIN `blog_blog` ON (`blog_entry`.`blog_id` = `blog_blog`.`id`)

链式获取外键数据

比如下面的 model:

class City(models.Model):
pass class Person(models.Model):
hometown = models.ForeignKey(
City, on_delete=models.SET_NULL, blank=True, null=True) class Book(models.Model):
author = models.ForeignKey(Person, on_delete=models.CASCADE)

我们可以通过以下语句来将 Book 关联的 Person,以及该条 Person 数据关联的 City 数据一起查询出来:

book = Book.objects.select_related("author__hometown").get(id=4)
person = book.author
city = person.hometown

因为我们在第一步查询的时候,通过双下划线将两个外键字段连接在一起取了出来,所以在第二步和第三步取 Person 数据和 City 数据的时候,就不需要再次查询数据库了。

同时获取多个外键关联字段

如果一个 model 有两个外键字段 foo 和 bar,那么下面的两种写法都将这两个外键字段关联取出:

select_related("foo", "bar")
select_related("foo").select_related("bar")

需要注意的是,这个链式的操作和 order_by() 的结果是不一样的哦,前面提到的 order_by() 的链式操作会导致后面的覆盖前面的,但是取外键数据的时候会同时取出。

注意: select_related() 仅作用于 ForeignKey 和 OneToOne,如果是 ManyToMany 字段,则需要用到下面的 prefetch_related() 函数。

prefetch_related()

prefetch_related() 和 select_related() 作用类似,都是通过减少查询的次数,来实现查询优化。

但 prefetch_related() 是针对 ManyToMany 的操作。

举个例子:

from django.db import models

class Topping(models.Model):
name = models.CharField(max_length=30) class Pizza(models.Model):
name = models.CharField(max_length=50)
toppings = models.ManyToManyField(Topping) def __str__(self):
return "%s (%s)" % (
self.name,
", ".join(topping.name for topping in self.toppings.all()),
)

当我们执行:

Pizza.objects.all()

的时候,因为每一条 Pizza 数据实例化的时候,都会调用 str() 函数,而这个函数会再次去请求一遍数据库,所以多条 Pizza 数据会导致查询多次数据库。

因为我们可以使用 prefetch_related() 函数来达到减少查询的目的:

Pizza.objects.prefetch_related('toppings').all()

这样的话,对数据库的查询会减少到两次,一次是查询出所有的 Pizza 数据,一次是根据所有的 pizza_id 找到所有关联的 topping 数据。

如果有兴趣,可以比对下面两条语句在 shell 中执行的时候,MySQL 服务器接收到的 SQL 查询语句:

Pizza.objects.all()

Pizza.objects.prefetch_related('toppings').all()

下面一种情况需要注意哦:

pizzas = Pizza.objects.prefetch_related('toppings')
[list(pizza.toppings.filter(spicy=True)) for pizza in pizzas]

因为第二步操作里,会对 toppings 数据进行一次新的 filter 过滤操作,所以会导致每次该语句重新去查询数据库,也就是说,我们的 prefetch_related() 操作是失效的。

以上就是本篇笔记全部内容,接下来会介绍查询里的 defer 和 only 函数。

本文首发于本人微信公众号:Django笔记。

原文链接:Django笔记十一之外键查询优化select_related和prefetch_related

如果想获取更多相关文章,可扫码关注阅读:

Django笔记十一之外键查询优化select_related和prefetch_related的更多相关文章

  1. django 笔记5 外键 ForeignKey

    class UsserGroup(models.Model): uid = models.AutoField(primary_key=True) caption = models.CharField( ...

  2. ORM数据库查询优化only与defer(select_related与prefetch_related)

    目录 一:数据库查询优化 1.ORM语句特点 2.only 3.defer 4.only与defer区别 5.select_related与prefetch_related 6.select_rela ...

  3. Django笔记&教程 4-3 模型(models)主键外键

    Django 自学笔记兼学习教程第4章第3节--模型(models)主键外键 点击查看教程总目录 参考:https://docs.djangoproject.com/en/2.2/ref/models ...

  4. Django中的select_related与prefetch_related

      Django是一个基于Python的网站开发框架,一个很重要的特点就是Battery Included,简单来说就是包含了常规开发中所需要的一切东西,包括但不限于完整的ORM模型.中间件.会话处理 ...

  5. Django的 select_related 和 prefetch_related 函数对 QuerySet 查询的优化(一)

    在数据库有外键的时候,使用 select_related() 和 prefetch_related() 可以很好的减少数据库请求的次数,从而提高性能.本文通过一个简单的例子详解这两个函数的作用.虽然Q ...

  6. pythonのdjango select_related 和 prefetch_related()

    在数据库有外键的时候,使用select_related() 和 prefetch_related() 可以很好的减少数据库请求次数,从而提高性能. (1)select_related()当执行它的查询 ...

  7. Django框架详细介绍---ORM相关操作---select_related和prefetch_related函数对 QuerySet 查询的优化

    Django的 select_related 和 prefetch_related 函数对 QuerySet 查询的优化 引言 在数据库存在外键的其情况下,使用select_related()和pre ...

  8. Django的select_related 和 prefetch_related 函数优化查询

    在数据库有外键的时候,使用 select_related() 和 prefetch_related() 可以很好的减少数据库请求的次数,从而提高性能.本文通过一个简单的例子详解这两个函数的作用.虽然Q ...

  9. 转载 :实例详解Django的 select_related 和 prefetch_related 函数对 QuerySet 查询的优化(一)

    在数据库有外键的时候,使用 select_related() 和 prefetch_related() 可以很好的减少数据库请求的次数,从而提高性能.本文通过一个简单的例子详解这两个函数的作用.虽然Q ...

  10. 详解Django的 select_related 和 prefetch_related 函数对 QuerySet 查询的优化

    在数据库有外键的时候,使用 select_related() 和 prefetch_related() 可以很好的减少数据库请求的次数,从而提高性能.本文通过一个简单的例子详解这两个函数的作用. 1. ...

随机推荐

  1. js中宏任务,微任务,异步,同步,执行的顺序

     [微任务]包括:Promise ,    process.nextTick() *node.js里面的  [宏任务]包括:整体代码script,  setTimeout    setInterval ...

  2. Ext中数据表格序号超过3位数时显示省略号

    问题 在老项目上优化时,遇到了Ext中ColumnModel序号超过3位数时,自动显示了省略号,不友好 修改方法 给RowNumberer添加样式 new Ext.grid.RowNumberer({ ...

  3. 狐漠漠养成日记 Cp.00001 开始养成计划

    开始养成计划 今天是我开始这个"狐漠漠养成计划"的第一天(划掉). 看来是昨天出门前忘记保存了,昨天写的几百字内容全都没有了,今天其实已经是计划开始的第二天了. 因为昨天晚上出去喝 ...

  4. 如何运用Vue自定义组件以及组件的传值

    Vue自定义组件 引入组件 首先在项目内的components新建.vue文件. 创建完成之后搭建完整的框架.其实就是新建组件,在此之前,需要在VScode中引入一个插件(vue 2 snippets ...

  5. mysql 递归

    MySQL中实现递归查询   对于数据库中的树形结构数据,如部门表,有时候,我们需要知道某部门的所有下属部分或者某部分的所有上级部门,这时候就需要用到mysql的递归查询 1.创建表 DROP TAB ...

  6. Maven 中央仓库配置

    Maven 中央仓库配置 Maven 中央仓库地址大全 <!-- 1.阿里中央仓库(首推1) --> <repository> <id>alimaven</i ...

  7. C Ⅷ

    数组  int number[100];   //这个数组可以放100个数 int x; int cnt = 0; double sum = 0; scanf("%d", & ...

  8. .Net5.0 上传图片、文件到服务器

    今天来看看.net上传图片到服务器的方式 public class ControlPresetUploadInput { /// <summary> /// 通道编号 /// </s ...

  9. Chrome(谷歌)浏览器永久关闭恢复页面提示框(记录)

    使用脚本调用Chrome浏览器启动指定页面,然后代码里的命令关闭,会被浏览器识别为非正常关闭. 再次执行脚本的时候会停留在空白页面,无法进入指定页面,设置为主页也无法进入. 排查可能是浏览器自动恢复页 ...

  10. docker&docker-compose安装

    一.docker安装 1.通过 uname -r 命令查看当前的内核版本,Docker 要求 CentOS 系统的内核版本高于 3.10 uname -r 2.查看系统是否安装过docker yum ...