django使用了两次INNER JOIN来完成请求,但是未指定的外键则不会被添加到结果中,例如张三的故乡.
pythonのdjango select_related 和 prefetch_related()
Posted 。低调ヽ继续
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了pythonのdjango select_related 和 prefetch_related()相关的知识,希望对你有一定的参考价值。
在数据库有外键的时候,使用select_related() 和 prefetch_related() 可以很好的减少数据库请求次数,从而提高性能。
(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代码如下:
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中添加如下数据:
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\');
select_related()
文章解释:https://www.cnblogs.com/tuifeideyouran/p/4232028.html
对于一对一字段(OneToOneField)和外键字段(ForeignKey),可以使用select_related()来对QuerySet进行优化.
在对QuerySet使用select_related()函数后,Django会获取相应外键对应的对象,从而在之后需要的时候不必再查询数据库了。
But我实际应用时:
并没有发现什么好处
不用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失效,只保留最后一个。
prefetch_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
可以通过传入一个None来清空之前的prefetch_related。就像这样:
prefetch_cleared_qset = zhangs.prefetch_related(None)
select_related()的效率要高于prefetch_related()。因此,最好在能用select_related()的地方尽量使用它,也就是说,对于ForeignKey字段,避免使用prefetch_related()。
以上是关于pythonのdjango select_related 和 prefetch_related()的主要内容,如果未能解决你的问题,请参考以下文章
pythonのdjango select_related 和 prefetch_related()