django使用了两次INNER JOIN来完成请求,可是未指定的外键则不会被添加到结果中,例如张三的故乡.
在数据库有外键的时候,使用select_related() 和 prefetch_related() 能够很好的减小数据库请求次数,从而提升性能。html
(1)select_related()当执行它的查询时它沿着外键关系查询关联的对象数据。它会生成一个复杂的查询并引发性能的消耗,可是在之后使用外键关系时将不须要数据库查询。
(2)prefetch_related()返回的也是QuerySet,它将在单个批处理中自动检索每一个指定查找的对象。这具备与select_related相似的目的,二者都被设计为阻止由访问相关对象而致使的数据库查询的泛滥,可是策略是彻底不一样的。
(3)select_related经过建立SQL链接并在SELECT语句中包括相关对象的字段来工做。所以,select_related在同一数据库查询中获取相关对象。然而,为了不因为跨越“多个'关系而致使的大得多的结果集,select_related限于单值关系 -外键和一对一关系。
(4)prefetch_related,另外一方面,为每一个关系单独查找,并在Python中“加入”。这容许它预取多对多和多对一对象,除了外键和一对一关系,它们不能使用select_related来完成。数据库
依据图示,编写model代码,model.py代码以下:django
1 from django.db import models 2 3 # Create your models here. 4 5 class Province(models.Model): 6 name = models.CharField(max_length=10) 7 def __str__(self): 8 return self.name 9 10 class City(models.Model): 11 name = models.CharField(max_length=5) 12 province = models.ForeignKey(Province,on_delete=True,null=True) 13 def __str__(self): 14 return self.name 15 16 class Person(models.Model): 17 firstname = models.CharField(max_length=10) 18 lastname = models.CharField(max_length=10) 19 visitation = models.ManyToManyField(City,related_name="visitor") # visitation字段与city表时多对多关系 20 hometown = models.ForeignKey(City,related_name="birth",on_delete=True,null=True) # related_name 是直接给外键起好名字 21 living = models.ForeignKey(City,related_name="citizen",on_delete=True,null=True) 22 def __str__(self): 23 return self.firstname + self.lastname
app名称为stark,咱们在province中添加以下数据:缓存
INSERT INTO `modeltest`.`stark_province` (`id`, `name`) VALUES ('1', '北京'); INSERT INTO `modeltest`.`stark_province` (`id`, `name`) VALUES ('2', '河南');
在city中添加以下数据:app
INSERT INTO `modeltest`.`stark_city` (`id`, `name`, `province_id`) VALUES ('1', '昌平', '1'); INSERT INTO `modeltest`.`stark_city` (`id`, `name`, `province_id`) VALUES ('2', '海淀', '1'); INSERT INTO `modeltest`.`stark_city` (`id`, `name`, `province_id`) VALUES ('3', '郑州', '2'); INSERT INTO `modeltest`.`stark_city` (`id`, `name`, `province_id`) VALUES ('4', '焦做', '2');
文章解释:https://www.cnblogs.com/tuifeideyouran/p/4232028.htmlide
对于一对一字段(OneToOneField)和外键字段(ForeignKey),可使用select_related()来对QuerySet进行优化.函数
在对QuerySet使用select_related()函数后,Django会获取相应外键对应的对象,从而在以后须要的时候没必要再查询数据库了。post
But我实际应用时:性能
并无发现什么好处fetch
不用select_related()状况的一个例子:
obj1 = models.City.objects.all() for c in obj1: print(c.province)
这样会致使线性的SQL查询,SQL查询语句以下:
(0.001) SELECT @@SQL_AUTO_IS_NULL; args=None (0.000) SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; args=None (0.000) SELECT `stark_city`.`id`, `stark_city`.`name`, `stark_city`.`province_id` FROM `stark_city`; args=() (0.001) SELECT `stark_province`.`id`, `stark_province`.`name` FROM `stark_province` WHERE `stark_province`.`id` = 1; args=(1,) (0.001) SELECT `stark_province`.`id`, `stark_province`.`name` FROM `stark_province` WHERE `stark_province`.`id` = 1; args=(1,) (0.000) SELECT `stark_province`.`id`, `stark_province`.`name` FROM `stark_province` WHERE `stark_province`.`id` = 2; args=(2,)
用select_related()的例子:
obj2 = models.City.objects.select_related().all() for c1 in obj2: print(c1.province)
控制台打印结果以下:
(0.001) SELECT `stark_province`.`id`, `stark_province`.`name` FROM `stark_province` WHERE `stark_province`.`id` = 2; args=(2,) (0.001) SELECT `stark_city`.`id`, `stark_city`.`name`, `stark_city`.`province_id` FROM `stark_city`; args=() (0.000) SELECT `stark_province`.`id`, `stark_province`.`name` FROM `stark_province` WHERE `stark_province`.`id` = 1; args=(1,) (0.000) SELECT `stark_province`.`id`, `stark_province`.`name` FROM `stark_province` WHERE `stark_province`.`id` = 1; args=(1,) (0.001) SELECT `stark_province`.`id`, `stark_province`.`name` FROM `stark_province` WHERE `stark_province`.`id` = 2; args=(2,) (0.001) SELECT `stark_province`.`id`, `stark_province`.`name` FROM `stark_province` WHERE `stark_province`.`id` = 2; args=(2,) 北京 北京 河南 河南
通过对比SQL,几乎呈现一直状态,并未实现我参考的文章中所出现的:
感觉不到这种优越感~~~~~
select_related() 接受可变长参数,每一个参数是须要获取的外键(父表的内容)的字段名,以及外键的外键的字段名、外键的外键的外键…。若要选择外键的外键须要使用两个下划线“__”来链接。
例如得到张三的现居省份
print("------------------------") zhangs = models.Person.objects.select_related("hometown__province","living__province").get(firstname="张",lastname="三") print(zhangs)
控制台数据SQL以下:
(0.000) SELECT @@SQL_AUTO_IS_NULL; args=None (0.000) SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; args=None (0.003) SELECT `stark_person`.`id`, `stark_person`.`firstname`, `stark_person`.`lastname`,
`stark_person`.`hometown_id`, `stark_person`.`living_id`, `stark_city`.`id`, `stark_city`.`name`,
`stark_city`.`province_id`, `stark_province`.`id`, `stark_province`.`name`, T4.`id`, T4.`name`,
T4.`province_id`, T5.`id`, T5.`name` FROM `stark_person` LEFT OUTER JOIN `stark_city` ON
(`stark_person`.`hometown_id` = `stark_city`.`id`) LEFT OUTER JOIN `stark_province` ON
(`stark_city`.`province_id` = `stark_province`.`id`) LEFT OUTER JOIN `stark_city` T4 ON
(`stark_person`.`living_id` = T4.`id`) LEFT OUTER JOIN `stark_province` T5 ON
(T4.`province_id` = T5.`id`) WHERE (`stark_person`.`firstname` = '张' AND `stark_person`.`lastname` = '三'); args=('张', '三') [08/Jan/2019 14:36:03] "GET /index/ HTTP/1.1" 200 2 张三
另外,django1.7之后两个外键同时关联使用时能够采用如下方法:
print("------------------------") zhangs = models.Person.objects.select_related("living__province").select_related("hometown__province").get(firstname="张",lastname="三") print(zhangs) return HttpResponse("ok")
django使用了三次LEFT OUTER JOIN来完成请求。而以前这边使用的INNER JOIN 来完成请求,两种关联查询区别在于:
-------------------------------------------------------------------额外知识点SQL-------------------------------------------------------------------
Table A 是左边的表。Table B 是右边的表。
1.INNER JOIN 产生的结果是AB的交集
SELECT * FROM TableA INNER JOIN TableB ON TableA.name = TableB.name
INNER JOIN 产生的结果是AB的交集 INNER JOIN 产生的结果是AB的交集
2.LEFT [OUTER] JOIN 产生表A的彻底集,而B表中匹配的则有值,没有匹配的则以null值取代。
SELECT * FROM TableA LEFT OUTER JOIN TableB ON TableA.name = TableB.name
LEFT [OUTER] JOIN 产生表A的彻底集,而B表中匹配的则有值 LEFT [OUTER] JOIN 产生表A的彻底集,而B表中匹配的则有值
3.RIGHT [OUTER] JOIN 产生表B的彻底集,而A表中匹配的则有值,没有匹配的则以null值取代。
SELECT * FROM TableA RIGHT OUTER JOIN TableB ON TableA.name = TableB.name
与left join相似。
4.FULL [OUTER] JOIN 产生A和B的并集。对于没有匹配的记录,则会以null作为值。
SELECT * FROM TableA FULL OUTER JOIN TableB ON TableA.name = TableB.name
你能够经过is NULL将没有匹配的值找出来:
SELECT * FROM TableA FULL OUTER JOIN TableB ON TableA.name = TableB.name
WHERE TableA.id IS null OR TableB.id IS null
FULL [OUTER] JOIN 产生A和B的并集 FULL [OUTER] JOIN 产生A和B的并集
5. CROSS JOIN 把表A和表B的数据进行一个N*M的组合,即笛卡尔积。如本例会产生4*4=16条记录,在开发过程当中咱们确定是要过滤数据,因此这种不多用。
SELECT * FROM TableA CROSS JOIN TableB
相信你们对inner join、outer join和cross join的区别一目了然了。
-------------------------------------------------------------------end-------------------------------------------------------------------
因为我不知道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失效,只保留最后一个。
对于多对多字段(ManyToManyField)和一对多(ForeignKey)字段,可使用prefetch_related()来进行优化.
prefetch_related()和select_related()的设计目的很类似,都是为了减小SQL查询的数量,可是实现的方式不同。后者 是经过JOIN语句,在SQL查询内解决问题。可是对于多对多关系,使用SQL语句解决就显得有些不太明智,由于JOIN获得的表将会很长,会致使SQL 语句运行时间的增长和内存占用的增长。如有n个对象,每一个对象的多对多字段对应Mi条,就会生成Σ(n)Mi 行的结果表。
prefetch_related()的解决方法是,分别查询每一个表,而后用Python处理他们之间的关系。
若是咱们要得到张三全部去过的城市.
zhangs = models.Person.objects.prefetch_related("visitation").get(firstname="张",lastname="三") for city in zhangs.visitation.all(): print(city)
控制台输出以下:
------------------------ (0.001) SELECT @@SQL_AUTO_IS_NULL; args=None (0.000) SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; args=None (0.002) SELECT `stark_person`.`id`, `stark_person`.`firstname`, `stark_person`.`lastname`, `stark_person`.`hometown_id`,
`stark_person`.`living_id`
FROM `stark_person`
WHERE (`stark_person`.`firstname` = '张' AND `stark_person`.`lastname` = '三'); args=('张', '三')
昌平 海淀 郑州
(0.001) SELECT (`stark_person_visitation`.`person_id`) AS `_prefetch_related_val_person_id`, `stark_city`.`id`,
`stark_city`.`name`, `stark_city`.`province_id`
FROM `stark_city`
INNER JOIN `stark_person_visitation` ON (`stark_city`.`id` = `stark_person_visitation`.`city_id`)
WHERE `stark_person_visitation`.`person_id` IN (1); args=(1,) [08/Jan/2019 15:55:03] "GET /index/ HTTP/1.1" 200 2
第一条SQL查询仅仅是获取张三的Person对象,第二条比较关键,它选取关系表`stark_person_visitation`中`person_id`为张三的行,而后和`city`表内联(INNER JOIN 也叫等值链接)获得结果表.
或者咱们要得到河南的全部城市名
hn = models.Province.objects.prefetch_related("city_set").get(name__iexact='河南') for city in hn.city_set.all(): print(city.name)
控制台输出SQL语句以下:
------------------------ (0.000) SELECT @@SQL_AUTO_IS_NULL; args=None (0.000) SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; args=None (0.001) SELECT `stark_province`.`id`, `stark_province`.`name` FROM `stark_province` WHERE `stark_province`.`name` LIKE '河南'; args=('河南',) 郑州 (0.001) SELECT `stark_city`.`id`, `stark_city`.`name`, `stark_city`.`province_id`
FROM `stark_city` WHERE `stark_city`.`province_id` IN (2); args=(2,) 焦做 [08/Jan/2019 16:04:50] "GET /index/ HTTP/1.1" 200 2
例如要得到全部姓王的人去过的省:
wangs = models.Person.objects.prefetch_related("visitation__province").filter(firstname__iexact='王') for i in wangs : print(i) for city in i.visitation.all(): print(city.province.name)
控制台SQL代码以下:
************************* (0.001) SELECT `stark_person`.`id`, `stark_person`.`firstname`, `stark_person`.`lastname`, `stark_person`.`hometown_id`, `stark_person`.`living_id` FROM `stark_person` WHERE `stark_person`.`firstname` LIKE '王'; args=('王',) (0.001) SELECT (`stark_person_visitation`.`person_id`) AS `_prefetch_related_val_person_id`, `stark_city`.`id`, `stark_city`.`name`, `stark_city`.`province_id` FROM `stark_city` INNER JOIN `stark_person_visitation` ON (`stark_city`.`id` = `stark_person_visitation`.`city_id`) WHERE `stark_person_visitation`.`person_id` IN (2); args=(2,) (0.001) SELECT `stark_province`.`id`, `stark_province`.`name` FROM `stark_province` WHERE `stark_province`.`id` IN (1, 2); args=(1, 2) [08/Jan/2019 17:06:03] "GET /index/ HTTP/1.1" 200 2 Person object (2) 北京 河南 河南
要注意的是,在使用QuerySet的时候,一旦在链式操做中改变了数据库请求,以前用prefetch_related缓存的数据将会被忽略掉。这会导 致Django从新请求数据库来得到相应的数据,从而形成性能问题。这里提到的改变数据库请求指各类filter()、exclude()等等最终会改变 SQL代码的操做。而all()并不会改变最终的数据库请求,所以是不会致使从新请求数据库的。
能够经过传入一个None来清空以前的prefetch_related。就像这样:
prefetch_cleared_qset = zhangs.prefetch_related(None)
select_related()的效率要高于prefetch_related()。所以,最好在能用select_related()的地方尽可能使用它,也就是说,对于ForeignKey字段,避免使用prefetch_related()。
django使用了两次INNER JOIN来完成请求,可是未指定的外键则不会被添加到结果中,例如张三的故乡.