Django系列 4:ORM之select_related方法

參考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遞歸的限制和性能的浪

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容