激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

腳本之家,腳本語言編程技術及教程分享平臺!
分類導航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服務器之家 - 腳本之家 - Python - 用實例詳解Python中的Django框架中prefetch_related()函數對數據庫查詢的優化

用實例詳解Python中的Django框架中prefetch_related()函數對數據庫查詢的優化

2020-05-27 09:58CuGBabyBeaR Python

這篇文章主要介紹了用實例詳解Python中的Django框架中prefetch_related()函數對數據庫查詢的優化,可減少對數據庫的查詢次數從而優化性能,需要的朋友可以參考下

實例的背景說明

假定一個個人信息系統,需要記錄系統中各個人的故鄉、居住地、以及到過的城市。數據庫設計如下:

用實例詳解Python中的Django框架中prefetch_related()函數對數據庫查詢的優化

Models.py 內容如下:
 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from django.db import models
 
class Province(models.Model):
 name = models.CharField(max_length=10)
 def __unicode__(self):
  return self.name
 
class City(models.Model):
 name = models.CharField(max_length=5)
 province = models.ForeignKey(Province)
 def __unicode__(self):
  return self.name
 
class Person(models.Model):
 firstname = models.CharField(max_length=10)
 lastname = models.CharField(max_length=10)
 visitation = models.ManyToManyField(City, related_name = "visitor")
 hometown = models.ForeignKey(City, related_name = "birth")
 living  = models.ForeignKey(City, related_name = "citizen")
 def __unicode__(self):
  return self.firstname + self.lastname

注1:創建的app名為“QSOptimize”

注2:為了簡化起見,`qsoptimize_province` 表中只有2條數據:湖北省和廣東省,`qsoptimize_city`表中只有三條數據:武漢市、十堰市和廣州市

prefetch_related()

對于多對多字段(ManyToManyField)和一對多字段,可以使用prefetch_related()來進行優化。或許你會說,沒有一個叫OneToManyField的東西啊。實際上 ,ForeignKey就是一個多對一的字段,而被ForeignKey關聯的字段就是一對多字段了。

 
作用和方法

prefetch_related()和select_related()的設計目的很相似,都是為了減少SQL查詢的數量,但是實現的方式不一樣。后者是通過JOIN語句,在SQL查詢內解決問題。但是對于多對多關系,使用SQL語句解決就顯得有些不太明智,因為JOIN得到的表將會很長,會導致SQL語句運行時間的增加和內存占用的增加。若有n個對象,每個對象的多對多字段對應Mi條,就會生成Σ(n)Mi 行的結果表。

prefetch_related()的解決方法是,分別查詢每個表,然后用Python處理他們之間的關系。繼續以上邊的例子進行說明,如果我們要獲得張三所有去過的城市,使用prefetch_related()應該是這么做:
 

?
1
2
3
4
>>> zhangs = Person.objects.prefetch_related('visitation').get(firstname=u"張",lastname=u"三")
>>> for city in zhangs.visitation.all() :
...  print city
...

上述代碼觸發的SQL查詢如下:
 

?
1
2
3
4
5
6
7
8
9
10
SELECT `QSOptimize_person`.`id`, `QSOptimize_person`.`firstname`,
`QSOptimize_person`.`lastname`, `QSOptimize_person`.`hometown_id`, `QSOptimize_person`.`living_id`
FROM `QSOptimize_person`
WHERE (`QSOptimize_person`.`lastname` = '三' AND `QSOptimize_person`.`firstname` = '張');
 
SELECT (`QSOptimize_person_visitation`.`person_id`) AS `_prefetch_related_val`, `QSOptimize_city`.`id`,
`QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`
FROM `QSOptimize_city`
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`)
WHERE `QSOptimize_person_visitation`.`person_id` IN (1);

第一條SQL查詢僅僅是獲取張三的Person對象,第二條比較關鍵,它選取關系表`QSOptimize_person_visitation`中`person_id`為張三的行,然后和`city`表內聯(INNER JOIN 也叫等值連接)得到結果表。
 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
+----+-----------+----------+-------------+-----------+
| id | firstname | lastname | hometown_id | living_id |
+----+-----------+----------+-------------+-----------+
| 1 | 張    | 三    |      3 |     1 |
+----+-----------+----------+-------------+-----------+
1 row in set (0.00 sec)
 
+-----------------------+----+-----------+-------------+
| _prefetch_related_val | id | name   | province_id |
+-----------------------+----+-----------+-------------+
|           1 | 1 | 武漢市  |      1 |
|           1 | 2 | 廣州市  |      2 |
|           1 | 3 | 十堰市  |      1 |
+-----------------------+----+-----------+-------------+
3 rows in set (0.00 sec)

顯然張三武漢、廣州、十堰都去過。

又或者,我們要獲得湖北的所有城市名,可以這樣:
 

?
1
2
3
4
>>> hb = Province.objects.prefetch_related('city_set').get(name__iexact=u"湖北省")
>>> for city in hb.city_set.all():
...  city.name
...

觸發的SQL查詢:
 

?
1
2
3
4
5
6
7
SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name`
FROM `QSOptimize_province`
WHERE `QSOptimize_province`.`name` LIKE '湖北省' ;
 
SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`
FROM `QSOptimize_city`
WHERE `QSOptimize_city`.`province_id` IN (1);

得到的表:
 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
+----+-----------+
| id | name   |
+----+-----------+
| 1 | 湖北省  |
+----+-----------+
1 row in set (0.00 sec)
 
+----+-----------+-------------+
| id | name   | province_id |
+----+-----------+-------------+
| 1 | 武漢市  |      1 |
| 3 | 十堰市  |      1 |
+----+-----------+-------------+
2 rows in set (0.00 sec)

我們可以看見,prefetch使用的是 IN 語句實現的。這樣,在QuerySet中的對象數量過多的時候,根據數據庫特性的不同有可能造成性能問題。

 
使用方法
*lookups 參數

prefetch_related()在Django < 1.7 只有這一種用法。和select_related()一樣,prefetch_related()也支持深度查詢,例如要獲得所有姓張的人去過的省:
 

?
1
2
3
4
5
>>> zhangs = Person.objects.prefetch_related('visitation__province').filter(firstname__iexact=u'張')
>>> for i in zhangs:
...  for city in i.visitation.all():
...   print city.province
...

觸發的SQL:
 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
SELECT `QSOptimize_person`.`id`, `QSOptimize_person`.`firstname`,
`QSOptimize_person`.`lastname`, `QSOptimize_person`.`hometown_id`, `QSOptimize_person`.`living_id`
FROM `QSOptimize_person`
WHERE `QSOptimize_person`.`firstname` LIKE '張' ;
 
SELECT (`QSOptimize_person_visitation`.`person_id`) AS `_prefetch_related_val`, `QSOptimize_city`.`id`,
`QSOptimize_city`.`name`, `QSOptimize_city`.`province_id` FROM `QSOptimize_city`
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`)
WHERE `QSOptimize_person_visitation`.`person_id` IN (1, 4);
 
SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name`
FROM `QSOptimize_province`
WHERE `QSOptimize_province`.`id` IN (1, 2);

獲得的結果:
 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
+----+-----------+----------+-------------+-----------+
| id | firstname | lastname | hometown_id | living_id |
+----+-----------+----------+-------------+-----------+
| 1 | 張    | 三    |      3 |     1 |
| 4 | 張    | 六    |      2 |     2 |
+----+-----------+----------+-------------+-----------+
2 rows in set (0.00 sec)
 
+-----------------------+----+-----------+-------------+
| _prefetch_related_val | id | name   | province_id |
+-----------------------+----+-----------+-------------+
|           1 | 1 | 武漢市  |      1 |
|           1 | 2 | 廣州市  |      2 |
|           4 | 2 | 廣州市  |      2 |
|           1 | 3 | 十堰市  |      1 |
+-----------------------+----+-----------+-------------+
4 rows in set (0.00 sec)
 
+----+-----------+
| id | name   |
+----+-----------+
| 1 | 湖北省  |
| 2 | 廣東省  |
+----+-----------+
2 rows in set (0.00 sec)

值得一提的是,鏈式prefetch_related會將這些查詢添加起來,就像1.7中的select_related那樣。

要注意的是,在使用QuerySet的時候,一旦在鏈式操作中改變了數據庫請求,之前用prefetch_related緩存的數據將會被忽略掉。這會導致Django重新請求數據庫來獲得相應的數據,從而造成性能問題。這里提到的改變數據庫請求指各種filter()、exclude()等等最終會改變SQL代碼的操作。而all()并不會改變最終的數據庫請求,因此是不會導致重新請求數據庫的。

舉個例子,要獲取所有人訪問過的城市中帶有“市”字的城市,這樣做會導致大量的SQL查詢:
 

?
1
2
plist = Person.objects.prefetch_related('visitation')
[p.visitation.filter(name__icontains=u"市") for p in plist]

因為數據庫中有4人,導致了2+4次SQL查詢:
 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
SELECT `QSOptimize_person`.`id`, `QSOptimize_person`.`firstname`, `QSOptimize_person`.`lastname`,
`QSOptimize_person`.`hometown_id`, `QSOptimize_person`.`living_id`
FROM `QSOptimize_person`;
 
SELECT (`QSOptimize_person_visitation`.`person_id`) AS `_prefetch_related_val`, `QSOptimize_city`.`id`,
`QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`
FROM `QSOptimize_city`
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`)
WHERE `QSOptimize_person_visitation`.`person_id` IN (1, 2, 3, 4);
 
SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`
FROM `QSOptimize_city`
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`)
WHERE(`QSOptimize_person_visitation`.`person_id` = 1 AND `QSOptimize_city`.`name` LIKE '%市%' );
 
SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`
FROM `QSOptimize_city`
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`)
WHERE (`QSOptimize_person_visitation`.`person_id` = 2 AND `QSOptimize_city`.`name` LIKE '%市%' );
 
SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`
FROM `QSOptimize_city`
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`)
WHERE (`QSOptimize_person_visitation`.`person_id` = 3 AND `QSOptimize_city`.`name` LIKE '%市%' );
 
SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`
FROM `QSOptimize_city`
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`)
WHERE (`QSOptimize_person_visitation`.`person_id` = 4 AND `QSOptimize_city`.`name` LIKE '%市%' );

詳細分析一下這些請求事件。

眾所周知,QuerySet是lazy的,要用的時候才會去訪問數據庫。運行到第二行Python代碼時,for循環將plist看做iterator,這會觸發數據庫查詢。最初的兩次SQL查詢就是prefetch_related導致的。

雖然已經查詢結果中包含所有所需的city的信息,但因為在循環體中對Person.visitation進行了filter操作,這顯然改變了數據庫請求。因此這些操作會忽略掉之前緩存到的數據,重新進行SQL查詢。

但是如果有這樣的需求了應該怎么辦呢?在Django >= 1.7,可以通過下一節的Prefetch對象來實現,如果你的環境是Django < 1.7,可以在Python中完成這部分操作。
 

?
1
2
plist = Person.objects.prefetch_related('visitation')
[[city for city in p.visitation.all() if u"市" in city.name] for p in plist]

Prefetch 對象

在Django >= 1.7,可以用Prefetch對象來控制prefetch_related函數的行為。

注:由于我沒有安裝1.7版本的Django環境,本節內容是參考Django文檔寫的,沒有進行實際的測試。

Prefetch對象的特征:

  •     一個Prefetch對象只能指定一項prefetch操作。
  •     Prefetch對象對字段指定的方式和prefetch_related中的參數相同,都是通過雙下劃線連接的字段名完成的。
  •     可以通過 queryset 參數手動指定prefetch使用的QuerySet。
  •     可以通過 to_attr 參數指定prefetch到的屬性名。
  •     Prefetch對象和字符串形式指定的lookups參數可以混用。

繼續上面的例子,獲取所有人訪問過的城市中帶有“武”字和“州”的城市:
 

?
1
2
3
4
5
6
7
wus = City.objects.filter(name__icontains = u"武")
zhous = City.objects.filter(name__icontains = u"州")
plist = Person.objects.prefetch_related(
  Prefetch('visitation', queryset = wus, to_attr = "wu_city"),
  Prefetch('visitation', queryset = zhous, to_attr = "zhou_city"),)
[p.wu_city for p in plist]
[p.zhou_city for p in plist]

注:這段代碼沒有在實際環境中測試過,若有不正確的地方請指正。

順帶一提,Prefetch對象和字符串參數可以混用。
None

可以通過傳入一個None來清空之前的prefetch_related。就像這樣:
 

?
1
>>> prefetch_cleared_qset = qset.prefetch_related(None)

小結

  1.     prefetch_related主要針一對多和多對多關系進行優化。
  2.     prefetch_related通過分別獲取各個表的內容,然后用Python處理他們之間的關系來進行優化。
  3.     可以通過可變長參數指定需要select_related的字段名。指定方式和特征與select_related是相同的。
  4.     在Django >= 1.7可以通過Prefetch對象來實現復雜查詢,但低版本的Django好像只能自己實現。
  5.     作為prefetch_related的參數,Prefetch對象和字符串可以混用。
  6.     prefetch_related的鏈式調用會將對應的prefetch添加進去,而非替換,似乎沒有基于不同版本上區別。
  7.     可以通過傳入None來清空之前的prefetch_related。

延伸 · 閱讀

精彩推薦
Weibo Article 1 Weibo Article 2 Weibo Article 3 Weibo Article 4 Weibo Article 5 Weibo Article 6 Weibo Article 7 Weibo Article 8 Weibo Article 9 Weibo Article 10 Weibo Article 11 Weibo Article 12 Weibo Article 13 Weibo Article 14 Weibo Article 15 Weibo Article 16 Weibo Article 17 Weibo Article 18 Weibo Article 19 Weibo Article 20 Weibo Article 21 Weibo Article 22 Weibo Article 23 Weibo Article 24 Weibo Article 25
主站蜘蛛池模板: 毛片视频网站 | 欧美精品一区二区三区在线 | 成人免费在线视频播放 | japan护士性xxxⅹhd| 日韩一级片一区二区三区 | 国产成人午夜高潮毛片 | 性 毛片| 黄色毛片免费看 | 国产精品a一 | 一级片九九 | 国产一级αv片免费观看 | 在线免费日本 | 最近中文字幕一区二区 | 欧美精品久久久久久久久久 | 天天夜碰日日摸日日澡性色av | 免费观看视频网站 | 在线91视频| 在线播放中文 | av7777777| 欧美日日操 | 中国a毛片 | 欧美黄色大片免费观看 | 欧美精品激情在线 | 黄色免费在线视频网站 | 亚洲第一综合 | 成人免费在线视频播放 | 国产婷婷一区二区三区 | 黄色免费视频网站 | 国产正在播放 | 牛牛视频在线 | 免费国产成人高清在线看软件 | 亚洲小视频网站 | 老子午夜影院 | 亚洲综合视频一区 | 电影av在线 | 欧美一级黄色录像片 | 特一级毛片 | 内地av在线 | 黄色二区三区 | 久久毛片 | 亚洲国产精品一 |