参考链接: https://blog.csdn.net/secretx/article/details/43964607

在数据库有外键的时候,使用select_related()和prefech_related()可以很好地减少数据库请求的次数, 从而提高性能

假定一个个人信息系统,需要记录系统中各个人的故乡,居住地,以及到过的城市,数据库设计如下:

from django.db import models

class Province(models.Model):
name = models.CharField(max_length=10)
def __unicode__(self):
return self.name class City(models.Model):
name = models.CharField(max_length=5)
province = models.ForeignKey(Province)
def __unicode__(self):
return self.name class Person(models.Model):
firstname = models.CharField(max_length=10)
lastname = models.CharField(max_length=10)
visitation = models.ManyToManyField(City, related_name = "visitor")
hometown = models.ForeignKey(City, related_name = "birth")
living = models.ForeignKey(City, related_name = "citizen")
def __unicode__(self):

select_related():

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

在对QuerySet使用select_related()函数后,Django会获取相应外键对应的对象,从而在之后需要的地方不必在查询数据库,如果我们需要打印数据库中的所有市及其所属省份,最直接的做法是:

citys = City.objects.all()
>>> for c in citys:
... print c.province

这样会导致线性的SQL查询, 如果对象数量n太多,每个对象中有K个外键字段的话,就会导致n*k+1次SQL查询,在本例子中,因为3个city对象就导致了4次SQL查询:

SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`
FROM `QSOptimize_city` SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name`
FROM `QSOptimize_province`
WHERE `QSOptimize_province`.`id` = 1 ; SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name`
FROM `QSOptimize_province`
WHERE `QSOptimize_province`.`id` = 2 ; SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name`
FROM `QSOptimize_province`
WHERE `QSOptimize_province`.`id` = 1 ;

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

citys = City.objects.select_related().all()
>>> for c in citys:
... print c.province

就只有一次SQL查询,虽然大大减少了SQL查询的次数:

SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`,
`QSOptimize_city`.`province_id`, `QSOptimize_province`.`id`, `QSOptimize_province`.`name`
FROM`QSOptimize_city`
INNER JOIN `QSOptimize_province` ON (`QSOptimize_city`.`province_id` = `QSOptimize_province`.`id`) ;

select_related() 接受可变长参数,每个参数是需要获取的外键(父表的内容)的字段名,以及外键的外键的字段名、外键的外键的外键…。若要选择外键的外键需要使用两个下划线“__”来连接。

例如我们要获得张三的现居省份,可以用如下方式:

  1. >>> zhangs = Person.objects.select_related('living__province').get(firstname=u"张",lastname=u"三")
  2. >>> zhangs.living.province

触发的SQL查询如下:

  1. SELECT `QSOptimize_person`.`id`, `QSOptimize_person`.`firstname`,
  2. `QSOptimize_person`.`lastname`, `QSOptimize_person`.`hometown_id`, `QSOptimize_person`.`living_id`,
  3. `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`, `QSOptimize_province`.`id`,
  4. `QSOptimize_province`.`name`
  5. FROM `QSOptimize_person`
  6. INNER JOIN `QSOptimize_city` ON (`QSOptimize_person`.`living_id` = `QSOptimize_city`.`id`)
  7. INNER JOIN `QSOptimize_province` ON (`QSOptimize_city`.`province_id` = `QSOptimize_province`.`id`)
  8. WHERE (`QSOptimize_person`.`lastname` = '三' AND `QSOptimize_person`.`firstname` = '张' );

可以看到,Django使用了2次 INNER JOIN 来完成请求,获得了city表和province表的内容并添加到结果表的相应列,这样在调用 zhangs.living的时候也不必再次进行SQL查询。

+----+-----------+----------+-------------+-----------+----+-----------+-------------+----+-----------+
| id | firstname | lastname | hometown_id | living_id | id | name | province_id | id | name |
+----+-----------+----------+-------------+-----------+----+-----------+-------------+----+-----------+
| 1 | 张 | 三 | 3 | 1 | 1 | 武汉市 | 1 | 1 | 湖北省 |
+----+-----------+----------+-------------+-----------+----+-----------+-------------+----+-----------+
1 row in set (0.00 sec)

然而,未指定的外键则不会被添加到结果中。这时候如果需要获取张三的故乡就会进行SQL查询了:

  1. >>> zhangs.hometown.province
  2.  
  3. SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`,
  4. `QSOptimize_city`.`province_id`
  5. FROM `QSOptimize_city`
  6. WHERE `QSOptimize_city`.`id` = 3 ;
  7.  
  8. SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name`
  9. FROM `QSOptimize_province`
  10. WHERE `QSOptimize_province`.`id` = 1

同时,如果不指定外键,就会进行两次查询。如果深度更深,查询的次数更多。

值得一提的是,从Django 1.7开始,select_related()函数的作用方式改变了。在本例中,如果要同时获得张三的故乡和现居地的省份,在1.7以前你只能这样做:

  1. >>> zhangs = Person.objects.select_related('hometown__province','living__province').get(firstname=u"张",lastname=u"三")
  2. >>> zhangs.hometown.province
  3. >>> zhangs.living.province

但是1.7及以上版本,你可以像和queryset的其他函数一样进行链式操作:

  1. >>> zhangs = Person.objects.select_related('hometown__province').select_related('living__province').get(firstname=u"张",lastname=u"三")
  2. >>> zhangs.hometown.province
  3. >>> zhangs.living.province

如果你在1.7以下版本这样做了,你只会获得最后一个操作的结果,在本例中就是只有现居地而没有故乡。在你打印故乡省份的时候就会造成两次SQL查询。

depth 参数

select_related() 接受depth参数,depth参数可以确定select_related的深度。Django会递归遍历指定深度内的所有的OneToOneField和ForeignKey。以本例说明:

>>> zhangs = Person.objects.select_related(depth = d)

d=1  相当于 select_related(‘hometown’,'living’)

d=2  相当于 select_related(‘hometown__province’,'living__province’)

无参数

select_related() 也可以不加参数,这样表示要求Django尽可能深的select_related。例如:zhangs = Person.objects.select_related().get(firstname=u”张”,lastname=u”三”)。但要注意两点:

1.Django本身内置一个上限,对于特别复杂的表关系,Django可能在你不知道的某处跳出递归,从而与你想的做法不一样。具体限制是怎么工作的我表示不清楚。
    2.Django并不知道你实际要用的字段有哪些,所以会把所有的字段都抓进来,从而会造成不必要的浪费而影响性能。

小结

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

Django 中的select_related函数优化查询的更多相关文章

  1. Django中的prefetch_related()函数优化

    对于多对多字段(ManyToManyField)和一对多字段, 可以使用prefetch_related()来进行优化 prefetch_related()和select_related()的设计目的 ...

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

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

  3. Django中的select_related与prefetch_related

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

  4. Django中获取参数(路径,查询,请求头,请求体)

    一.通常HTTP协议向服务器传参有几种途径 : 提取URL的特定部分,如/weather/shanghai/2018,可以在服务器端的路由中用正则表达式截取: 查询字符串(query string), ...

  5. 在Django中使用F()函数

    F()允许Django在未实际链接数据的情况下具有对数据库字段的值的引用.通常情况下我们在更新数据时需要先从数据库里将原数据取出后方在内存里,然后编辑某些属性,最后提交.例如这样 # Tintin f ...

  6. Django中静态文件引用优化

    静态文件引用优化 在html文件中是用django的静态文件路径时,一般会这么写: <script type="text/javascript" src="/sta ...

  7. Django中前端界面实现级联查询

    Django前端界面实现级联查询 一.前端界面中 <span scope="col" colspan="6"> 院系:<select id=& ...

  8. Django中的path函数

    path( )作用:解析URL地址 path( ) 标准语法: (<>为必须的参数,[]为可选参数) path(<route>, <view>, [name=Non ...

  9. Django中的ORM关系映射查询方面

    ORM:Object-Relation Mapping:对象-关系映射 在MVC框架中的Model模块中都包括ORM,对于开发人员主要带来了如下好处: 实现了数据模型与数据库的解耦,通过简单的配置就可 ...

随机推荐

  1. Linux - seq

    1. 概述 生成序列的命令 之前貌似写过 seq 命令, 这次单独拉出来再写一次吧 节操先放一放 2. 命令 1. 帮助 命令 # 输出我就不打了, # 这个命令可用, 证明组件可用 > seq ...

  2. ACM的探索之Keen On Evrything But Triangle(我觉得可以很接近啦!!)

    #include<bits/stdc++.h> using namespace std; int main() { int n,q,l,r; while(cin>>n>& ...

  3. Wx-小程序-长按复制文本

    view: <text bindlongtap='copy' data-name='{{name}}'>{{item.name}}</text> js: copy(e) { v ...

  4. mybatis - MybatisAutoConfiguration

    一. MybatisProperties 在使用 mybatis 时, 还需要对mapper进行配置: mybatis: mapper-locations: classpath:mapper/**Ma ...

  5. 解决tensorflow Saver.restore()无效的问题

    解决tensorflow 的 Saver.restore()无法从本地读取变量的问题 最近做tensorflow 手写数字识别的时候遇到了一个问题,Saver的restore()方法无法从本地恢复变量 ...

  6. spark streaming读取kakfka数据手动维护offset

    在spark streaming读取kafka的数据中,spark streaming提供了两个接口读取kafka中的数据,分别是KafkaUtils.createDstream,KafkaUtils ...

  7. X86汇编指令集大全【转】

    [原文地址]https://blog.csdn.net/bjbz_cxy/article/details/79467688[原文地址] ---------- 一.数据传输指令 ------------ ...

  8. Centos7下载和安装教程

    https://blog.csdn.net/qq_42570879/article/details/82853708 阿里下载64bit镜像:http://mirrors.aliyun.com/cen ...

  9. JAVA单例实现方式(常用)

    JAVA单例实现方式(常用) public class Singleton { // Q1:为什么要使用volatile关键字? private volatile static Singleton u ...

  10. 移动端,ajax 动态加载的元素,为动态添加的一系列同个类名的元素添加点击事件

    背景:一个列表页,有一系列同类名的元素,需要为每一个动态添加的列表项添加事件: 点击选择下图中不同的文档类型,再通过 ajax 动态加载不同的文档. 使用过的方法: 1.通知 jquery 的 $(s ...