Django:select_related() 的用法和执行时间性能

Posted

技术标签:

【中文标题】Django:select_related() 的用法和执行时间性能【英文标题】:Django: Usage Of select_related() And Execution Time Performance 【发布时间】:2015-03-01 17:10:25 【问题描述】:

我是 Django 和数据库的新手,所以我试图对性能有所了解。具体来说,我想了解select_related() 是否按照我认为的方式工作。

这是我的模型的简化版本:

class User(models.Model):
    short = models.CharField(max_length=255)
    name = models.CharField(max_length=255)

class Comment(models.Model):
    title = models.CharField(max_length=255)
    content = models.TextField()
    short = models.ForeignKey(User)

在我的模板中,我需要在评论标题旁边显示用户的简称。我的测试数据库大小有 1000 个用户和 19000 个 cmets。

起初,我按如下方式检索列表:

cmt_list = Comment.objects.all().order_by('title')

我的模板正在访问short 外键关系,这导致对数据库的额外命中。检索所有数据需要 ~30 秒。这太可怕了。

我知道这比原始 SQL 快得多,但我不知道如何通过 Django ORM 来做到这一点。所以,我使用了低级接口:

from django.db import connection

cursor = connection.cursor()
cursor.execute("SELECT app_comment.title,app_user.short       \
                       FROM app_comment,app_user              \
                       WHERE app_comment.short_id=app_user.id \
                       ORDER BY app_comment.title"
              )
raw_list = cursor.fetchall()
cmt_list = [ "title":entry[0], "short":entry[1] for entry in raw_list]

检索所有数据需要 ~233 毫秒。这正是我所期待的!

阅读更多文档后,我发现了 Django 中的 select_related() 功能。 所以,我试过了:

cmt_list = Comment.objects.all().select_related('short__short').order_by('title')

检索所有数据需要 ~1.3 秒。比原来的要好得多,但与原始 SQL 查询相比仍然很慢。

问题

我在使用 select_related()/Django ORM 的方式上做错了什么?我知道 ORM 会增加一些开销,但是 1.3s 与 233ms 似乎过多。或者,这是意料之中的,我只需要克服它?

我将如何使用 ORM 设计一个与我创建的原始 SQL 查询等效的查询?鉴于我对 select_related() 的了解,我的原始 SQL 查询和我的 Django 查询应该大致相同。 (Django 查询将抓取更多内容,但对于我的测试数据,不会检索到太多额外内容。)

【问题讨论】:

您可以通过打印Comment.objects.select_related('short').order_by('title').query来查看生成的查询。 如果您想测试差异是否是由额外的数据开销引起的(例如,Django 正在获取content),请查看.only().defer()。跨度> 啊哈!我没有意识到看到 Django 正在做的查询是多么容易。这将使我的测试更容易。谢谢@KevinChristopherHenry。 【参考方案1】:

实际上,ORM 会导致相当多的开销。以下语句产生相同的查询(您可以通过比较 str(cmt_list.query) 进行检查):

cmt_list1 = Comment.objects.all()
cmt_list2 = Comment.objects.values('id', 'title', 'content', 'short_id')

但是,使用timeit.timeit(在我的本地项目中使用不同的模型)进行的简单测试表明,第二种方法比第一种方法快两倍多。

别忘了,与intvarchar(255) 列相比,TextField 包含大量数据。我敢肯定,如果您在原始 sql 查询中获取 content 列,数字会更接近。

【讨论】:

【参考方案2】:

按照答案中的建议,我使用 Django ORM 更新了我的查询。我必须添加一些列表理解来为我的模板提供相同的输入。结果如下:

    cmt_list = [ "title":entry["title"], "short":entry["short__short"] 
                 for entry in 
                 Comment.objects.order_by('title').values("title", 'short__short')
               ]

执行查询的时间是 ~280 毫秒,这与我使用原始 SQL 可以获得的结果更加一致。

为了确定 Django ORM 背后的原始 SQL 是什么,我使用了以下打印语句...

print(Comment.objects.order_by('title').values("title", 'short__short').query)

结果如下:

SELECT "app_comment"."title", "app_user"."short" 
FROM "app_comment" INNER JOIN "app_user" 
ON ( "app_comment"."short_id" = "app_user"."id" ) 
ORDER BY "app_comment"."title" ASC

这和我生的一样。

这个解决方案根本不需要select_related()

【讨论】:

以上是关于Django:select_related() 的用法和执行时间性能的主要内容,如果未能解决你的问题,请参考以下文章

Django:select_related() 的用法和执行时间性能

如何使用'select_related'从相关(ForeignKey)django模型中接收并非所有字段

django 2.1 上的 select_related 不工作

Django中的左外反向select_related?

如何在模板 Django 中使用 select_related?

Django 通用外键和 select_related