Django之Model操作之prefetch_related的應用
參考博客:?Django操作進階與查詢優(yōu)化
對于多對多字段(ManyToManyField)和一對多字段润歉,可以使用prefetch_related()來進行優(yōu)化治力。
作用和方法:
prefetch_related()和select_related()的設(shè)計目的很相似,都是為了減少SQL查詢的數(shù)量,但是實現(xiàn)的方式不一樣线脚。后者是通過JOIN語句,在SQL查詢內(nèi)解決問題技肩。
但是對于多對多關(guān)系撇吞,使用SQL語句解決就顯得有些不太明智,因為JOIN得到的表將會很長堆巧,會導致SQL語句運行時間的增加和內(nèi)存占用的增加妄荔。若有n個對象,每個對象的多對多字段對應Mi條谍肤,就會生成Σ(n)Mi 行的結(jié)果表啦租。
關(guān)于prefetch_related 使用方法的不同造成 SQL查詢語句的不同:
1: 不帶任何參數(shù), model.objects.prefetch_related().all()
假設(shè)查詢的結(jié)果有n個?Person 對象
a:多對多,? 這種情況荒揣,會造成若干次查詢刷钢,那么共查詢1 + n 次SQL(本例查詢有2個?Person 對象),除了第一次查詢?Person對應的表乳附,其他的SQL使用了?JOIN ON 來對關(guān)聯(lián)表(?orm_practice_person_visitation?)查詢
b:直接外鍵内地,?這種情況,會造成若1 + n次查詢赋除,?除了第一次查詢阱缓,其他次都是正常的where id=xxx 的普通查詢
c:?外鍵的外鍵,這種情況举农,會造成若1 + 2 * n次查詢荆针,?除了第一次查詢,其他每個對象都會單獨查詢2次(外鍵與外鍵的外鍵關(guān)聯(lián)表)的where id=xxx 的普通查詢
查詢1:多對多
person_objs = Person.objects.prefetch_related().filter(firstname__contains='蘭').all()??
# person_objs = Person.objects.prefetch_related().all()
for p in person_objs:
????????????for i in p.visitation.all():? ??#? 多對多
????????????????????????print(i.name)
[2021-11-06 13:09:02,797] [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` FROM `orm_practice_person` WHERE `orm_practice_person`.`firstname` LIKE BINARY '%蘭%'; args=('%蘭%',)
[2021-11-06 13:09:02,799] [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` INNER JOIN `orm_practice_person_visitation` ON (`orm_practice_city`.`id` = `orm_practice_person_visitation`.`city_id`) WHERE `orm_practice_person_visitation`.`person_id` = 13; args=(13,)
[2021-11-06 13:09:02,800] [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` INNER JOIN `orm_practice_person_visitation` ON (`orm_practice_city`.`id` = `orm_practice_person_visitation`.`city_id`) WHERE `orm_practice_person_visitation`.`person_id` = 17; args=(17,)
查詢2:外鍵
person_objs = Person.objects.prefetch_related().filter(firstname__contains='蘭')
for p in person_objs:
????????????print(p.hometown.name)
? ? ? ? ? ? # print(p.needs.orderinfo)
[2021-11-06 13:42:37,332] [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` FROM `orm_practice_person` WHERE `orm_practice_person`.`firstname` LIKE BINARY '%蘭%'; args=('%蘭%',)
[2021-11-06 13:42:37,333] [utils.py:123] [utils:debug_sql] DEBUG (0.000) SELECT `orm_practice_order`.`id`, `orm_practice_order`.`customer_id`, `orm_practice_order`.`orderinfo`, `orm_practice_order`.`time` FROM `orm_practice_order` WHERE `orm_practice_order`.`id` = 23 LIMIT 21; args=(23,)
[2021-11-06 13:42:37,334] [utils.py:123] [utils:debug_sql] DEBUG (0.000) SELECT `orm_practice_order`.`id`, `orm_practice_order`.`customer_id`, `orm_practice_order`.`orderinfo`, `orm_practice_order`.`time` FROM `orm_practice_order` WHERE `orm_practice_order`.`id` = 36 LIMIT 21; args=(36,)
查詢3:外鍵的外鍵
person_objs = Person.objects.prefetch_related("hometown__province").filter(firstname__contains='蘭')
for pin person_objs:
????????????print(p.hometown.province.name)
[2021-11-06 13:52:54,060] [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` FROM `orm_practice_person` WHERE `orm_practice_person`.`firstname` LIKE BINARY '%蘭%'; args=('%蘭%',)
[2021-11-06 13:52:54,062] [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` WHERE `orm_practice_city`.`id` = 33 LIMIT 21; args=(33,)
[2021-11-06 13:52:54,063] [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` = 33 LIMIT 21; args=(33,)
[2021-11-06 13:52:54,064] [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` WHERE `orm_practice_city`.`id` = 47 LIMIT 21; args=(47,)
[2021-11-06 13:52:54,064] [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` = 47 LIMIT 21; args=(47,)
2:帶參數(shù),model.objects.prefetch_related(*lookups).all()
假設(shè)查詢的結(jié)果有n個??Person 對象
a: 多對多航背,這種情況喉悴,會造成2次查詢,并且第二次使用?JOIN ON? +?IN?來對關(guān)聯(lián)表(?orm_practice_person_visitation?)查詢
b:直接外鍵玖媚,這種情況箕肃,會造成2次查詢,并且第二次使用 IN?來對關(guān)聯(lián)表(?orm_practice_city ,??orm_practice_province)查詢
c:?直接外鍵的外鍵今魔,?這種情況勺像,?會造成3次查詢,并且第二次通過IN查詢外鍵關(guān)聯(lián)的表?(orm_practice_city)错森、第三次通過 IN?查詢外鍵的外鍵關(guān)聯(lián)的表(orm_practice_province )
查詢1:多對多
person_objs = Person.objects.prefetch_related("visitation").filter(firstname__contains='蘭').all()
# person_objs = Person.objects.prefetch_related("visitation").all()
for p in person_objs:
????????????for i in p.visitation.all():
????????????????????????print(i.name)
[2021-11-06 13:18:52,754] [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` FROM `orm_practice_person` WHERE `orm_practice_person`.`firstname` LIKE BINARY '%蘭%'; args=('%蘭%',)
[2021-11-06 13:18:52,756] [utils.py:123] [utils:debug_sql] DEBUG (0.000) SELECT (`orm_practice_person_visitation`.`person_id`) AS `_prefetch_related_val_person_id`, `orm_practice_city`.`id`, `orm_practice_city`.`name`, `orm_practice_city`.`province_id` FROM `orm_practice_city` INNER JOIN `orm_practice_person_visitation` ON (`orm_practice_city`.`id` = `orm_practice_person_visitation`.`city_id`) WHERE `orm_practice_person_visitation`.`person_id` IN (13, 17); args=(13, 17)
查詢2:外鍵
person_objs = Person.objects.prefetch_related().filter(firstname__contains='蘭')
for p in person_objs:
????????????print(p.hometown.name)
? ? ? ? ? ? # print(p.needs.orderinfo)
[2021-11-06 13:45:01,121] [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` FROM `orm_practice_person` WHERE `orm_practice_person`.`firstname` LIKE BINARY '%蘭%'; args=('%蘭%',)
[2021-11-06 13:45:01,122] [utils.py:123] [utils:debug_sql] DEBUG (0.000) SELECT `orm_practice_order`.`id`, `orm_practice_order`.`customer_id`, `orm_practice_order`.`orderinfo`, `orm_practice_order`.`time` FROM `orm_practice_order` WHERE `orm_practice_order`.`id` IN (36, 23); args=(36, 23)
查詢3:外鍵的外鍵
person_objs = Person.objects.prefetch_related("hometown__province").filter(firstname__contains='蘭')
for pin person_objs:
????????????print(p.hometown.province.name)
[2021-11-06 13:46:48,447] [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` FROM `orm_practice_person` WHERE `orm_practice_person`.`firstname` LIKE BINARY '%蘭%'; args=('%蘭%',)
[2021-11-06 13:46:48,449] [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` WHERE `orm_practice_city`.`id` IN (33, 47); args=(33, 47)
[2021-11-06 13:46:48,450] [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` IN (33, 47); args=(33, 47)
注意 QuerySet是lazy的吟宦,要用的時候才會去訪問數(shù)據(jù)庫。運行到第二行Python代碼時涩维,for循環(huán)將plist看做iterator殃姓,這會觸發(fā)數(shù)據(jù)庫查詢。最初的兩次SQL查詢就是prefetch_related導致的瓦阐。
雖然已經(jīng)查詢結(jié)果中包含所有所需的city的信息辰狡,但因為在循環(huán)體中對Person.visitation進行了filter操作,這顯然改變了數(shù)據(jù)庫請求垄分。因此這些操作會忽略掉之前緩存到的數(shù)據(jù)狡门,重新進行SQL查詢险掀。
常用使用方法:
1:model.objects.prefetch_related()
2:model.objects.prefetch_related('外鍵')
3:model.objects.prefetch_related('外鍵__外鍵')