參考1:ForeignKey,ManyToManyField和OneToOneField的辨析
參考2:Django文檔—Model中的ForeignKey、ManyToManyField、OneToOneField?
參考3:Django之Model操作之select_related的應(yīng)用
class Province(models.Model):
????????????name = models.CharField(max_length=10)
????????????def __unicode__(self):
????????????????????????return self.name
????????????def __str__(self):
????????????????????????return self.name
class City(models.Model):
????????????name = models.CharField(max_length=5)
????????????province = models.ForeignKey(Province, null=True, blank=True, on_delete=models.CASCADE)
????????????def __unicode__(self):
????????????????????????return self.name
class Order(models.Model):
????????????customer = models.ForeignKey("Person", on_delete=models.CASCADE)
????????????orderinfo = models.CharField(max_length=50)
????????????time = models.DateTimeField(auto_now_add=True)
????????????def __unicode__(self):
????????????????????????return self.orderinfo
class Person(models.Model):
????????????firstname = models.CharField(max_length=10)
????????????lastname = models.CharField(max_length=10)
????????????needs = models.ForeignKey(to=Order, related_name='require', null=True, blank=True, on_delete=models.DO_NOTHING)
????????????visitation = models.ManyToManyField(City, related_name="visitor", null=True, blank=True)
????????????hometown = models.ForeignKey(City, related_name="birth", on_delete=models.CASCADE)
????????????living = models.ForeignKey(City, related_name="citizen", on_delete=models.CASCADE)
????????????def __unicode__(self):
????????????????????????return self.firstname +self.lastname
對 select_related 的操作,主要針對以上model來進(jìn)行說明:
常用
model.tb.objects.all().select_related('外鍵字段')
model.tb.objects.all().select_related('外鍵字段__外鍵字段')
概念
對于一對一字段(OneToOneField)和外鍵字段(ForeignKey,多對一),可以使用select_related 來對QuerySet進(jìn)行優(yōu)化
在對QuerySet使用select_related()函數(shù)后,Django會獲取相應(yīng)外鍵對應(yīng)的對象,從而在之后需要的時候不必再查詢數(shù)據(jù)庫了
city_objs = City.objects.all()
for cin city_objs:
????????????print(c.province)
這樣會導(dǎo)致線性的SQL查詢,如果對象數(shù)量n太多,每個對象中有k個外鍵字段的話,就會導(dǎo)致n*k+1次SQL查詢。在本例中,因?yàn)榧僭O(shè)有30個city對象就導(dǎo)致了30 + 1 次SQL查詢
以下是部分sql日志
[2021-11-06 12:05:00,383] [utils.py:123] [utils:debug_sql] DEBUG (0.000) SELECT `orm_practice_city`.`id`, `orm_practice_city`.`name`, `orm_practice_city`.`province_id` FROM `orm_practice_city`; args=()
[2021-11-06 12:05:00,384] [utils.py:123] [utils:debug_sql] DEBUG (0.000) SELECT `orm_practice_province`.`id`, `orm_practice_province`.`name` FROM `orm_practice_province` WHERE `orm_practice_province`.`id` = 11 LIMIT 21; args=(11,)
[2021-11-06 12:05:00,385] [utils.py:123] [utils:debug_sql] DEBUG (0.000) SELECT `orm_practice_province`.`id`, `orm_practice_province`.`name` FROM `orm_practice_province` WHERE `orm_practice_province`.`id` = 12 LIMIT 21; args=(12,)
[2021-11-06 12:05:00,386] [utils.py:123] [utils:debug_sql] DEBUG (0.000) SELECT `orm_practice_province`.`id`, `orm_practice_province`.`name` FROM `orm_practice_province` WHERE `orm_practice_province`.`id` = 13 LIMIT 21; args=(13,)
[2021-11-06 12:05:00,387] [utils.py:123] [utils:debug_sql] DEBUG (0.000) SELECT `orm_practice_province`.`id`, `orm_practice_province`.`name` FROM `orm_practice_province` WHERE `orm_practice_province`.`id` = 14 LIMIT 21; args=(14,)
[2021-11-06 12:05:00,387] [utils.py:123] [utils:debug_sql] DEBUG (0.000) SELECT `orm_practice_province`.`id`, `orm_practice_province`.`name` FROM `orm_practice_province` WHERE `orm_practice_province`.`id` = 15 LIMIT 21; args=(15,)
[2021-11-06 12:05:00,388] [utils.py:123] [utils:debug_sql] DEBUG (0.000) SELECT `orm_practice_province`.`id`, `orm_practice_province`.`name` FROM `orm_practice_province` WHERE `orm_practice_province`.`id` = 16 LIMIT 21; args=(16,)
現(xiàn)在如果我們使用select_related()函數(shù):
city_objs = City.objects.select_related("province").all()
for cin city_objs:
????????????print(c.province)
就只有一次SQL查詢,顯然大大減少了SQL查詢的次數(shù),看到他做了了inner? join,把foreign key的表連接過來
[2021-11-06 12:12:39,386] [utils.py:123] [utils:debug_sql] DEBUG (0.000) SELECT `orm_practice_city`.`id`, `orm_practice_city`.`name`, `orm_practice_city`.`province_id`, `orm_practice_province`.`id`, `orm_practice_province`.`name` FROM `orm_practice_city` LEFT OUTER JOIN `orm_practice_province` ON (`orm_practice_city`.`province_id` = `orm_practice_province`.`id`); args=()
再比如:
zhu = Person.objects.select_related("living__province").get(id=10)
print(zhu.living.province)
[2021-11-06 12:15:22,440] [utils.py:123] [utils:debug_sql] DEBUG (0.000) SELECT `orm_practice_person`.`id`, `orm_practice_person`.`firstname`, `orm_practice_person`.`lastname`, `orm_practice_person`.`needs_id`, `orm_practice_person`.`hometown_id`, `orm_practice_person`.`living_id`, `orm_practice_city`.`id`, `orm_practice_city`.`name`, `orm_practice_city`.`province_id`, `orm_practice_province`.`id`, `orm_practice_province`.`name` FROM `orm_practice_person` INNER JOIN `orm_practice_city` ON (`orm_practice_person`.`living_id` = `orm_practice_city`.`id`) LEFT OUTER JOIN `orm_practice_province` ON (`orm_practice_city`.`province_id` = `orm_practice_province`.`id`) WHERE `orm_practice_person`.`id` = 10 LIMIT 21; args=(10,)
這樣查詢 living做關(guān)聯(lián)的時候就不用再查詢一次了;可以看到,Django使用了2次 INNER JOIN 來完成請求,獲得了city表和province表的內(nèi)容并添加到結(jié)果表的相應(yīng)列,這樣在調(diào)用zhu?.living的時候也不必再次進(jìn)行SQL查詢。
select_related() 支持三種方法:
1:指定 *fields 參數(shù)
這個參數(shù)是需要獲取的外鍵(父表內(nèi)容)的字段名,用來關(guān)聯(lián)外鍵的字段名,比如:
? ?- 有外鍵的外鍵。 比如這里的??
?zhu?=?Person.objects.select_related('living__province').get(firstname="zhu")
?zhu?=?Person.objects.select_related('living__province').get(firstname="zhu")
?zhu.living.province?//?沒有產(chǎn)生其他sql
<Province: 浙江>
然而,未指定的外鍵則不會被添加到結(jié)果中,就會產(chǎn)生一條查詢sql
?zhu.hometown.province
(0.000) SELECT?"select_related_province"."id",?"select_related_province"."name"?FROM "select_related_<Province: 浙江>
? ?- 有多個外鍵時需要指定到哪個外鍵做關(guān)聯(lián)(默認(rèn)全部都關(guān)聯(lián))
?zhu?=?Person.objects.select_related('living__province', 'hometown__province').all()??
或者
?zhu?=?Person.objects.select_related('living__province').select_related('hometown__province').all()
2 :指定 depth 參數(shù) (已廢棄了)
????select_related() 接受depth參數(shù),depth參數(shù)可以確定select_related的深度。Django會遞歸遍歷指定深度內(nèi)的所有的OneToOneField和ForeignKey
3 :不指定參數(shù)
select_related() 也可以不加參數(shù),這樣表示要求Django盡可能深的select_related
Django本身內(nèi)置一個上限,對于特別復(fù)雜的表關(guān)系,Django可能在你不知道的某處跳出遞歸,從而與你想的做法不一樣。具體限制是怎么工作的我表示不清楚。
Django并不知道你實(shí)際要用的字段有哪些,所以會把所有的字段都抓進(jìn)來,從而會造成不必要的浪費(fèi)而影響性能。
注意: 由于Person的外鍵字段needs與Order的外鍵字段customer是相互引用情況,在不給定具體參數(shù)時,默認(rèn)不會join (已測)
[2021-11-06 12:29:35,365] [utils.py:123] [utils:debug_sql] DEBUG (0.000) SELECT `orm_practice_person`.`id`, `orm_practice_person`.`firstname`, `orm_practice_person`.`lastname`, `orm_practice_person`.`needs_id`, `orm_practice_person`.`hometown_id`, `orm_practice_person`.`living_id`, `orm_practice_city`.`id`, `orm_practice_city`.`name`, `orm_practice_city`.`province_id`, T3.`id`, T3.`name`, T3.`province_id` FROM `orm_practice_person` INNER JOIN `orm_practice_city` ON (`orm_practice_person`.`hometown_id` = `orm_practice_city`.`id`) INNER JOIN `orm_practice_city` T3 ON (`orm_practice_person`.`living_id` = T3.`id`) WHERE `orm_practice_person`.`id` = 10 LIMIT 21; args=(10,)
小結(jié)
1: select_related主要針一對一和多對一(外鍵)關(guān)系進(jìn)行優(yōu)化。
2: select_related使用SQL的JOIN語句進(jìn)行優(yōu)化,通過減少SQL查詢的次數(shù)來進(jìn)行優(yōu)化、提高性能。
3 :可以通過可變長參數(shù)指定需要select_related的字段名。也可以通過使用雙下劃線“__”連接字段名來實(shí)現(xiàn)指定的遞歸查詢。沒有指定的字段不會緩存,沒有指定的深度不會緩存,如果要訪問的話Django會再次進(jìn)行SQL查詢。
4 :也接受無參數(shù)的調(diào)用,Django會盡可能深的遞歸查詢所有的字段。但注意有Django遞歸的限制和性能的浪