一、select_related查询优化
select_related通过多表join关联查询,一次性获得所有数据,通过降低数据库查询次数来提升性能,但关联表不能太多,因为join操作本来就比较消耗性能。本文通过Django debug toolbar工具来直观显示查询次数、查询语句,如果不会使用“Django debug toolbar”工具,可以翻看我之前写的博客,从而配置它!
1
2
3
|
model.tb.objects.
all
().select_related()
model.tb.objects.
all
().select_related(
'外键字段'
)
model.tb.objects.
all
().select_related(
'外键字段__外键字段'
)
|
models.py
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
31
32
33
|
from
django.db
import
models
class
Publisher(models.Model):
name
=
models.CharField(max_length
=
30
, verbose_name
=
"名称"
)
address
=
models.CharField(
"地址"
, max_length
=
50
)
city
=
models.CharField(
'城市'
, max_length
=
60
)
state_province
=
models.CharField(max_length
=
30
)
country
=
models.CharField(max_length
=
50
)
website
=
models.URLField()
class
Meta:
verbose_name
=
'出版商'
verbose_name_plural
=
verbose_name
def
__str__(
self
):
return
self
.name
class
Author(models.Model):
name
=
models.CharField(max_length
=
30
)
hobby
=
models.CharField(max_length
=
20
, default
=
"", blank
=
True
)
def
__str__(
self
):
return
self
.name
class
Book(models.Model):
title
=
models.CharField(max_length
=
100
, verbose_name
=
"书名"
)
authors
=
models.ManyToManyField(Author)
publisher
=
models.ForeignKey(Publisher, verbose_name
=
"出版社"
)
publication_date
=
models.DateField(null
=
True
)
price
=
models.DecimalField(max_digits
=
5
, decimal_places
=
2
, default
=
10
, verbose_name
=
"价格"
)
def
__str__(
self
):
return
self
.title
|
views.py
1
2
3
|
def
index(request):
obj
=
Book.objects.
all
()
return
render(request,
"index.html"
,
locals
())
|
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<!DOCTYPE html>
<html lang
=
"en"
>
<head>
<meta charset
=
"UTF-8"
>
<title>Title<
/
title>
<
/
head>
<body>
<p>django debug toolbar!<
/
p>
{
%
for
item
in
obj
%
}
<div>{{ item.title }} {{ item.price }} {{ item.publisher.name }}<
/
div>
{
%
endfor
%
}
<
/
body>
<
/
html>
|
当我们没有使用select_related时,在前端模板中,每一次循环就要向数据库发送一次请求,因为我表中数据很少,所有只发起了7次查询,但实际生产中每个表的数据肯定是成千上万的,传统的操作对数据库的性能影响很大!
当我们使用select_related连表操作时,请看下例,只发起了两次查询!!!
1
2
3
|
def
index(request):
obj
=
Book.objects.
all
().select_related(
"publisher"
)
return
render(request,
"index.html"
,
locals
())
|
总结:
select_related主要针一对一和多对一关系进行优化。
select_related使用SQL的JOIN语句进行优化,通过减少SQL查询的次数来进行优化、提高性能。
可以通过可变长参数指定需要select_related的字段名。也可以通过使用双下划线“__”连接字段名来实现指定的递归查询(也就是外键的外键,多层连表查询)。没有指定的字段不会缓存,没有指定的深度不会缓存,如果要访问的话Django会再次进行SQL查询。
二、prefetch_related查询优化
prefetch_related()和select_related()的设计目的很相似,都是为了减少SQL查询的数量,但是实现的方式不一样。后者是通过JOIN语句,在SQL查询内解决问题。但是对于多对多关系,使用SQL语句解决就显得有些不太明智,因为JOIN得到的表将会很长,会导致SQL语句运行时间的增加和内存占用的增加。prefetch_related()的解决方法是,分别查询每个表,然后用Python处理他们之间的关系!
1
|
models.tb.objects.prefetch_related(
'外键字段'
)
|
我们还是通过上例来举例:
1
2
3
|
def
index(request):
obj
=
Book.objects.
all
().prefetch_related(
"publisher"
)
return
render(request,
"index.html"
,
locals
())
|
使用prefetch_related优化查询,貌似发起了四次数据库请求,但实际是只有两次的,就是图中划横线的SQL语句,其他两条是session相关的,我们不用理会。我来解释一下prefetch_related是怎么发起请求的,第一步:先拿到book表的所有数据;第二步:通过select .. from ... where ... in (book表中所有出版社的外键ID)。这样通过分别发起两次请求,获取了book表以及和book表相关联的publisher表的数据(并不是所有publisher表数据,只有和book表相关联数据!),然后通过Python处理数据的对应关联。
总结:
prefetch_related主要针一对多和多对多关系进行优化。
prefetch_related通过分别获取各个表的内容,然后用Python处理他们之间的关系来进行优化。
大结局:
select_related是通过join来关联多表,一次获取数据,存放在内存中,但如果关联的表太多,会严重影响数据库性能。
prefetch_related是通过分表,先获取各个表的数据,存放在内存中,然后通过Python处理他们之间的关联。