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來完成請求,可是未指定的外鍵則不會被添加到結果中,例如張三的故鄉.