博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
实例详解Django的 select_related 和 prefetch_related 函数对 QuerySet 查询的优化(三)...
阅读量:7236 次
发布时间:2019-06-29

本文共 8234 字,大约阅读时间需要 27 分钟。

 

这是本系列的最后一篇,主要是select_related() 和 prefetch_related() 的最佳实践。

第一篇在 讲例子和select_related()

第二篇在 讲prefetch_related()

 

4. 一些实例

 

选择哪个函数

如果我们想要获得所有家乡是湖北的人,最无脑的做法是先获得湖北省,再获得湖北的所有城市,最后获得故乡是这个城市的人。就像这样:
1
2
3
4
5
>>> hb
=
Province.objects.get(name__iexact
=
u
"湖北省"
)
>>> people
=
[]
>>>
for
city
in
hb.city_set.
all
():
...   people.extend(city.birth.
all
())
...
显然这不是一个明智的选择,因为这样做会导致1+(湖北省城市数)次SQL查询。反正是个反例,导致的查询和获得掉结果就不列出来了。
 
prefetch_related() 或许是一个好的解决方法,让我们来看看。
1
2
3
4
5
>>> hb
=
Province.objects.prefetch_related(
"city_set__birth"
).objects.get(name__iexact
=
u
"湖北省"
)
>>> people
=
[]
>>>
for
city
in
hb.city_set.
all
():
...   people.extend(city.birth.
all
())
...
因为是一个深度为2的prefetch,所以会导致3次SQL查询:
1
2
3
4
5
6
7
8
9
10
11
12
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);
 
SELECT
`QSOptimize_person`.`id`, `QSOptimize_person`.`firstname`, `QSOptimize_person`.`lastname`,
`QSOptimize_person`.`hometown_id`, `QSOptimize_person`.`living_id`
FROM
`QSOptimize_person`
WHERE
`QSOptimize_person`.`hometown_id`
IN
(1, 3);
嗯…看上去不错,但是3次查询么?倒过来查询可能会更简单?
1
>>> people
=
list
(Person.objects.select_related(
"hometown__province"
).
filter
(hometown__province__name__iexact
=
u
"湖北省"
))
1
2
3
4
5
6
7
SELECT
`QSOptimize_person`.`id`, `QSOptimize_person`.`firstname`, `QSOptimize_person`.`lastname`,
`QSOptimize_person`.`hometown_id`, `QSOptimize_person`.`living_id`, `QSOptimize_city`.`id`,
`QSOptimize_city`.`
name
`, `QSOptimize_city`.`province_id`, `QSOptimize_province`.`id`, `QSOptimize_province`.`
name
`
FROM
`QSOptimize_person`
INNER
JOIN
`QSOptimize_city`
ON
(`QSOptimize_person`.`hometown_id` = `QSOptimize_city`.`id`)
INNER
JOIN
`QSOptimize_province`
ON
(`QSOptimize_city`.`province_id` = `QSOptimize_province`.`id`)
WHERE
`QSOptimize_province`.`
name
`
LIKE
'湖北省'
;
1
2
3
4
5
6
7
8
+
----+-----------+----------+-------------+-----------+----+--------+-------------+----+--------+
| id | firstname | lastname | hometown_id | living_id | id |
name  
| province_id | id |
name  
|
+
----+-----------+----------+-------------+-----------+----+--------+-------------+----+--------+
|  1 | 张        | 三       |           3 |         1 |  3 | 十堰市 |           1 |  1 | 湖北省 |
|  2 | 李        | 四       |           1 |         3 |  1 | 武汉市 |           1 |  1 | 湖北省 |
|  3 | 王        | 麻子     |           3 |         2 |  3 | 十堰市 |           1 |  1 | 湖北省 |
+
----+-----------+----------+-------------+-----------+----+--------+-------------+----+--------+
3
rows
in
set
(0.00 sec)
完全没问题。不仅SQL查询的数量减少了,python程序上也精简了。
select_related()的效率要高于prefetch_related()。因此,最好在能用select_related()的地方尽量使用它,也就是说,对于ForeignKey字段,避免使用prefetch_related()。
 
 
 

联用

对于同一个QuerySet,你可以同时使用这两个函数。
在我们一直使用的例子上加一个model:Order (订单)
1
2
3
4
5
6
class
Order(models.Model):
    
customer  
=
models.ForeignKey(Person)
    
orderinfo 
=
models.CharField(max_length
=
50
)
    
time      
=
models.DateTimeField(auto_now_add
=
True
)
    
def
__unicode__(
self
):
        
return
self
.orderinfo
如果我们拿到了一个订单的id 我们要知道这个订单的客户去过的省份。因为有ManyToManyField显然必须要用prefetch_related()。如果只用prefetch_related()会怎样呢?
1
2
3
4
>>> plist
=
Order.objects.prefetch_related(
'customer__visitation__province'
).get(
id
=
1
)
>>>
for
city
in
plist.customer.visitation.
all
():
...  
print
city.province.name
...
显然,关系到了4个表:Order、Person、City、Province,根据prefetch_related()的特性就得有4次SQL查询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SELECT
`QSOptimize_order`.`id`, `QSOptimize_order`.`customer_id`, `QSOptimize_order`.`orderinfo`, `QSOptimize_order`.`
time
`
FROM
`QSOptimize_order`
WHERE
`QSOptimize_order`.`id` = 1 ;
 
SELECT
`QSOptimize_person`.`id`, `QSOptimize_person`.`firstname`, `QSOptimize_person`.`lastname`, `QSOptimize_person`.`hometown_id`, `QSOptimize_person`.`living_id`
FROM
`QSOptimize_person`
WHERE
`QSOptimize_person`.`id`
IN
(1);
 
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);
 
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
26
27
28
29
30
+----+-------------+---------------+---------------------+
| id | customer_id | orderinfo     | time                |
+----+-------------+---------------+---------------------+
|  1 |           1 | Info of Order | 2014-08-10 17:05:48 |
+----+-------------+---------------+---------------------+
1 row in set (0.00 sec)
 
+----+-----------+----------+-------------+-----------+
| 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)
 
+----+--------+
| id | name   |
+----+--------+
|  1 | 湖北省 |
|  2 | 广东省 |
+----+--------+
2 rows in set (0.00 sec)

更好的办法是先调用一次select_related()再调用prefetch_related(),最后再select_related()后面的表

1
2
3
4
>>> plist
=
Order.objects.select_related(
'customer'
).prefetch_related(
'customer__visitation__province'
).get(
id
=
1
)
>>>
for
city
in
plist.customer.visitation.
all
():
...  
print
city.province.name
...

这样只会有3次SQL查询,Django会先做select_related,之后prefetch_related的时候会利用之前缓存的数据,从而避免了1次额外的SQL查询:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
SELECT
`QSOptimize_order`.`id`, `QSOptimize_order`.`customer_id`, `QSOptimize_order`.`orderinfo`, 
`QSOptimize_order`.`
time
`, `QSOptimize_person`.`id`, `QSOptimize_person`.`firstname`, 
`QSOptimize_person`.`lastname`, `QSOptimize_person`.`hometown_id`, `QSOptimize_person`.`living_id` 
FROM
`QSOptimize_order` 
INNER
JOIN
`QSOptimize_person`
ON
(`QSOptimize_order`.`customer_id` = `QSOptimize_person`.`id`) 
WHERE
`QSOptimize_order`.`id` = 1 ;
 
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);
 
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
+----+-------------+---------------+---------------------+----+-----------+----------+-------------+-----------+
| id | customer_id | orderinfo     | time                | id | firstname | lastname | hometown_id | living_id |
+----+-------------+---------------+---------------------+----+-----------+----------+-------------+-----------+
|  1 |           1 | Info of Order | 2014-08-10 17:05:48 |  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)
 
+----+--------+
| id | name   |
+----+--------+
|  1 | 湖北省 |
|  2 | 广东省 |
+----+--------+
2 rows in set (0.00 sec)

值得注意的是,可以在调用prefetch_related之前调用select_related,并且Django会按照你想的去做:先select_related,然后利用缓存到的数据prefetch_related。然而一旦prefetch_related已经调用,select_related将不起作用。

 

小结

  1. 因为select_related()总是在单次SQL查询中解决问题,而prefetch_related()会对每个相关表进行SQL查询,因此select_related()的效率通常比后者高。
  2. 鉴于第一条,尽可能的用select_related()解决问题。只有在select_related()不能解决问题的时候再去想prefetch_related()。
  3. 你可以在一个QuerySet中同时使用select_related()和prefetch_related(),从而减少SQL查询的次数。
  4. 只有prefetch_related()之前的select_related()是有效的,之后的将会被无视掉

转载于:https://www.cnblogs.com/stevenzeng/p/5163527.html

你可能感兴趣的文章
我为什么选择51cto网络视频教育
查看>>
linux安装opencv3
查看>>
一个令人纠结的性能性能缺陷
查看>>
ECharts
查看>>
好程序员教程分享Javascript设计模式
查看>>
ROM、PROM、EPROM、EEPROM、Flash ROM分别指什么?
查看>>
PHP环境配置遇到的问题与解决
查看>>
Linux 扩展LVM下的Volume Group、Logical Volume
查看>>
mysql备份和恢复
查看>>
再见,2012
查看>>
c语言:输出乘法口诀表
查看>>
Memcached学习笔记 — 第二部分:Memcached服务器安装
查看>>
我的友情链接
查看>>
b/s和c/s架构的理解和区别
查看>>
rsyncd.conf配置详细讲解
查看>>
List<String> 如何用jstl foreach遍历
查看>>
H2 Web Console to In Memory Database – Spring Boot
查看>>
我的友情链接
查看>>
mysql 运维常用命令收录
查看>>
linux第二周作业
查看>>