转载自https://blog.csdn.net/cugbabybear/article/details/38342793数据库
在数据库有外键的时候,使用 select_related() 和 prefetch_related() 能够很好的减小数据库请求的次数,从而提升性能。本文经过一个简单的例子详解这两个函数的做用。虽然QuerySet的文档中已经详细说明了,但本文试图从QuerySet触发的SQL语句来分析工做方式,从而进一步了解Django具体的运做方式。django
这是本系列的第一篇, 内容是 select_related() 函数的用途、实现途径、以及使用方法缓存
第二篇在这里,内容是 prefetch_related() 函数的用途、实现途径、以及使用方法app
第三篇在这里,用几个实例来讲明一些复杂查询最佳实践。数据库设计
1. 实例的背景说明
假定一个我的信息系统,须要记录系统中各我的的故乡、居住地、以及到过的城市。数据库设计以下:函数
Models.py 内容以下:性能
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):
return self.firstname + self.lastnamefetch
注1:建立的app名为“QSOptimize”优化
注2:为了简化起见,`qsoptimize_province` 表中只有2条数据:湖北省和广东省,`qsoptimize_city`表中只有三条数据:武汉市、十堰市和广州市.net
2. 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 ;
注:这里的SQL语句是直接从Django的logger:‘django.db.backends’输出出来的
若是咱们使用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`) ;
这里咱们能够看到,Django使用了INNER JOIN来得到省份的信息。顺便一提这条SQL查询获得的结果以下:
+----+-----------+-------------+----+-----------+
| id | name | province_id | id | name |
+----+-----------+-------------+----+-----------+
| 1 | 武汉市 | 1 | 1 | 湖北省 |
| 2 | 广州市 | 2 | 2 | 广东省 |
| 3 | 十堰市 | 1 | 1 | 湖北省 |
+----+-----------+-------------+----+-----------+
3 rows in set (0.00 sec)
使用方法
函数支持以下三种用法:
*fields 参数
select_related() 接受可变长参数,每一个参数是须要获取的外键(父表的内容)的字段名,以及外键的外键的字段名、外键的外键的外键...。若要选择外键的外键须要使用两个下划线“__”来链接。
例如咱们要得到张三的现居省份,能够用以下方式:
>>> zhangs = Person.objects.select_related('living__province').get(firstname=u"张",lastname=u"三")
>>> zhangs.living.province
触发的SQL查询以下:
SELECT `QSOptimize_person`.`id`, `QSOptimize_person`.`firstname`,
`QSOptimize_person`.`lastname`, `QSOptimize_person`.`hometown_id`, `QSOptimize_person`.`living_id`,
`QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`, `QSOptimize_province`.`id`,
`QSOptimize_province`.`name`
FROM `QSOptimize_person`
INNER JOIN `QSOptimize_city` ON (`QSOptimize_person`.`living_id` = `QSOptimize_city`.`id`)
INNER JOIN `QSOptimize_province` ON (`QSOptimize_city`.`province_id` = `QSOptimize_province`.`id`)
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查询了:
>>> zhangs.hometown.province
SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`,
`QSOptimize_city`.`province_id`
FROM `QSOptimize_city`
WHERE `QSOptimize_city`.`id` = 3 ;
SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name`
FROM `QSOptimize_province`
WHERE `QSOptimize_province`.`id` = 1
同时,若是不指定外键,就会进行两次查询。若是深度更深,查询的次数更多。
值得一提的是,从Django 1.7开始,select_related()函数的做用方式改变了。在本例中,若是要同时得到张三的故乡和现居地的省份,在1.7之前你只能这样作:
>>> zhangs = Person.objects.select_related('hometown__province','living__province').get(firstname=u"张",lastname=u"三")
>>> zhangs.hometown.province
>>> zhangs.living.province
可是1.7及以上版本,你能够像和queryset的其余函数同样进行链式操做:
>>> zhangs = Person.objects.select_related('hometown__province').select_related('living__province').get(firstname=u"张",lastname=u"三")
>>> zhangs.hometown.province
>>> 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"三")。但要注意两点:
Django自己内置一个上限,对于特别复杂的表关系,Django可能在你不知道的某处跳出递归,从而与你想的作法不同。具体限制是怎么工做的我表示不清楚。
Django并不知道你实际要用的字段有哪些,因此会把全部的字段都抓进来,从而会形成没必要要的浪费而影响性能。
小结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失效,只保留最后一个。--------------------- 做者:CuGBabyBeaR 来源:CSDN 原文:https://blog.csdn.net/cugbabybear/article/details/38342793 版权声明:本文为博主原创文章,转载请附上博文连接!