Python3.7.3
Django==2.0.7
select_related()函数优化QuerySet只针对一对一字段(OneToOneField)/外键字段(ForeignKey)
from django.db import models
class test(models.Model):
name = models.CharField(max_length=100)
class Tag(models.Model):
kk = models.OneToOneField(test,on_delete=models.CASCADE,default="")
name = models.CharField(max_length=100)
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
class Author(models.Model):
name = models.CharField(max_length=200)
email = models.EmailField()
class Entry(models.Model):
blog = models.ForeignKey(
Blog,
on_delete=models.CASCADE,
related_name="entryblogs",
related_query_name="entryqueryblogs"
)
headline = models.CharField(max_length=255)
body_text = models.TextField()
pub_date = models.DateField()
mod_date = models.DateField()
authors = models.ManyToManyField(Author)
n_comments = models.IntegerField()
n_pingbacks = models.IntegerField()
rating = models.IntegerField()
tag = models.OneToOneField(Tag,on_delete=models.CASCADE,default="")
queryset_entry = models.Entry.objects.all()
for foo in queryset_entry:
foo.blog.name
SQL语句
SELECT "app_entry"."id", "app_entry"."blog_id", "app_entry"."headline", "app_entry"."body_text", "app_entry"."pub_date", "app_entry"."mod_date", "app_entry"."n_comments", "app_entry"."n_pingbacks", "app_entry"."rating", "app_entry"."_order" FROM "app_entry" ORDER BY "app_entry"."_order" ASC; args=()
SELECT "app_blog"."id", "app_blog"."name", "app_blog"."tagline" FROM "app_blog" WHERE "app_blog"."id" = 1; args=(1,)
SELECT "app_blog"."id", "app_blog"."name", "app_blog"."tagline" FROM "app_blog" WHERE "app_blog"."id" = 1; args=(1,)
SELECT "app_blog"."id", "app_blog"."name", "app_blog"."tagline" FROM "app_blog" WHERE "app_blog"."id" = 1; args=(1,)
SELECT "app_blog"."id", "app_blog"."name", "app_blog"."tagline" FROM "app_blog" WHERE "app_blog"."id" = 1; args=(1,)
Entry表中存放外键blog,并且有四条数据,所以在迭代时,会执行4条SQL语句查询
queryset_entry = models.Entry.objects.select_related('blog').all()
for foo in queryset_entry:
foo.blog.name
SQL语句:
SELECT "app_entry"."id", "app_entry"."blog_id", "app_entry"."headline", "app_entry"."body_text", "app_entry"."pub_date", "app_entry"."mod_date", "app_entry"."n_comments", "app_entry"."n_pingbacks", "app_entry"."rating", "app_entry"."_order", "app_blog"."id", "app_blog"."name", "app_blog"."tagline" FROM "app_entry" INNER JOIN "app_blog" ON ("app_entry"."blog_id" = "app_blog"."id") ORDER BY "app_entry"."_order" ASC; args=()
由此得出,通过使用 select_related 减少SQL查询的次数来进行优化、提高性能
def select_related(self, *fields)
默认会认为是表中的所有外键,包括多层外键
models.Entry.objects.select_related().all()
传什么参数,就会优化相应的外键,没有传递的参数,不会被优化
关联多层外键,可以使用双下划线__
models.Entry.objects.select_related('blog').all()
models.Entry.objects.select_related('tag__kk').all()
这里使用 logger 打印SQL语句
在setting.py添加以下代码即可在终端看到执行的SQL语句
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console':{
'level':'DEBUG',
'class':'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'propagate': True,
'level':'DEBUG',
},
}
}
prefetch_related()函数优化QuerySet只针对多对多字段(ManyToManyField)
queryset_entry = models.Entry.objects.all()
for foo in queryset_entry:
foo.authors.all()
SQL语句
SELECT "app_entry"."id", "app_entry"."blog_id", "app_entry"."headline", "app_entry"."body_text", "app_entry"."pub_date", "app_entry"."mod_date", "app_entry"."n_comments", "app_entry"."n_pingbacks", "app_entry"."rating", "app_entry"."tag_id", "app_entry"."_order" FROM "app_entry" ORDER BY "app_entry"."_order" ASC; args=()
SELECT "app_author"."id", "app_author"."name", "app_author"."email" FROM "app_author" INNER JOIN "app_entry_authors" ON ("app_author"."id" = "app_entry_authors"."author_id") WHERE "app_entry_authors"."entry_id" = 1 LIMIT 21; args=(1,)
SELECT "app_author"."id", "app_author"."name", "app_author"."email" FROM "app_author" INNER JOIN "app_entry_authors" ON ("app_author"."id" = "app_entry_authors"."author_id") WHERE "app_entry_authors"."entry_id" = 2 LIMIT 21; args=(2,)
SELECT "app_author"."id", "app_author"."name", "app_author"."email" FROM "app_author" INNER JOIN "app_entry_authors" ON ("app_author"."id" = "app_entry_authors"."author_id") WHERE "app_entry_authors"."entry_id" = 3 LIMIT 21; args=(3,)
SELECT "app_author"."id", "app_author"."name", "app_author"."email" FROM "app_author" INNER JOIN "app_entry_authors" ON ("app_author"."id" = "app_entry_authors"."author_id") WHERE "app_entry_authors"."entry_id" = 4 LIMIT 21; args=(4,)
Entry表中存放外键authors,并且有四条数据,所以在迭代时,会执行4条SQL语句查询
queryset_entry = models.Entry.objects.prefetch_related('authors').all()
for foo in queryset_entry:
foo.authors.all()
SQL语句
SELECT "app_entry"."id", "app_entry"."blog_id", "app_entry"."headline", "app_entry"."body_text", "app_entry"."pub_date", "app_entry"."mod_date", "app_entry"."n_comments", "app_entry"."n_pingbacks", "app_entry"."rating", "app_entry"."tag_id", "app_entry"."_order" FROM "app_entry" ORDER BY "app_entry"."_order" ASC; args=()
SELECT ("app_entry_authors"."entry_id") AS "_prefetch_related_val_entry_id", "app_author"."id", "app_author"."name", "app_author"."email" FROM "app_author" INNER JOIN "app_entry_authors" ON ("app_author"."id" = "app_entry_authors"."author_id") WHERE "app_entry_authors"."entry_id" IN (1, 2, 3, 4); args=(1, 2, 3, 4)
prefetch_related() 的使用和 select_related()相似,都是为了减少SQL查询的数量,但是实现的方式不一样。后者是通过JOIN语句,在SQL查询内解决问题。但是对于多对多关系,使用SQL语句解决就显得有些不太明智,因为JOIN得到的表将会很长,会导致SQL语句运行时间的增量和内存占用的增加。若有n个对象,每个对象的多对多字段对应Mi条,就会生成Σ(n)Mi行的结果表.prefetch_related
F表达式在优化中使用场景如下:
在某个表中存在一个字段,这个字段的用途适用于记录博客访问量,字段会随着访客而自增(+1)
一个F()对象表示一个模型字段或注释的列的值。它可以引用模型字段值并使用它们执行数据库操作,而无需将它们从数据库中拉出到Python内存中。相反,Django使用该F()对象生成一个SQL表达式,该表达式描述了数据库级别所需的操作。
obj_entry = models.Entry.objects.all().first()
obj_entry.n_comments += 1
obj_entry.save()
在这里,我们 obj_entry.n_comments 将数据库的值从数据库中提取到内存中并使用熟悉的Python运算符对其进行操作,然后将对象保存回数据库。
obj_entry = models.Entry.objects.all().first()
obj_entry.n_comments = F("n_comments") 1
obj_entry.save()
F()操作在 obj.save() 后会持续存在
obj_entry = models.Entry.objects.all().first()
obj_entry.n_comments += 1
obj_entry.save() # n_comments == 2
obj_entry.save() # n_comments == 2
obj_entry.save() # n_comments == 2
obj_entry = models.Entry.objects.all().first()
obj_entry.n_comments = F("n_comments") 1
obj_entry.save() # n_comments == 2
obj_entry.save() # n_comments == 3
obj_entry.save() # n_comments == 4
obj_entry.save() # n_comments == 5
使用唯一的索引列检索单个对象
>>> entry = Entry.objects.get(id=10)
会比以下更快:
>>> entry = Entry.objects.get(headline="News Item Title")
因为它id是由数据库索引的,并保证是唯一的。
执行以下操作可能非常慢:
>>> entry = Entry.objects.get(headline__startswith="News")
首先,headline没有索引,这将使底层数据库获取更慢。
其次,查找不保证只返回一个对象。如果查询匹配多个对象,它将从数据库中检索并传输所有对象。如果返回数百或数千条记录,这种惩罚可能会很大。如果数据库位于单独的服务器上,那么惩罚将会加剧,其中网络开销和延迟也是一个因素。
QuerySet.exists()
效果好于:
len(queryset)
如果只需要外键值,请使用已有对象上的外键值,而不是获取整个相关对象并获取其主键。即做:
entry.blog.id
好于:
entry.blog_id
在可能的情况下创建对象时,请使用该 bulk_create()方法来减少SQL查询的数量。例如:
Entry.objects.bulk_create([
Entry(headline='This is a test'),
Entry(headline='This is only a test'),
])
好于:
Entry.objects.create(headline='This is a test')
Entry.objects.create(headline='This is only a test')
持久连接避免了在每个请求中重新建立与数据库的连接的开销。它们由CONN_MAX_AGE定义连接最大生命周期的参数控制 。可以为每个数据库单独设置。
没有持久化连接,每一个网站的请求都会与数据库建立一个连接。如果数据库不在本地,尽管网速很快,这也将花费20-75ms.
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'xxx',
'USER': 'root',
'PASSWORD': 'root',
'HOST': '127.0.0.1',
'PORT': '3306',
'CONN_MAX_AGE': 600, # 秒
}
}
CONN_MAX_AGE | 描述 |
---|---|
0(默认) | 保留在每个请求结束时关闭数据库连接的历史行为 |
600(正秒数) | 要启用持久连接的秒数 |
None | 对于无限制的持久连接 |