【问题标题】:Terrible Django Admin Performance糟糕的 Django 管理员性能
【发布时间】:2019-02-03 00:05:33
【问题描述】:

我正在开发一个 Django 项目,它已经获得了大量真实世界的数据,因此我可以看到它的性能。

少数 DjangoAdmin 列表的性能很糟糕。

我有一个管理员列表,我们称之为devices。在该列表中,我正在获取每一行的附加信息,这些字段与其他表相关,并通过 FK/PK/M2N 连接。

列表包含大约 500 条记录,根据django-debug-toolbar 的说法,加载该屏幕大约需要 6.5 秒,这是难以忍受的。

这个管理类

@admin.register(Device)
class DeviceAdmin(admin.ModelAdmin):
    list_select_related = True
    list_display = ('id', 'name', 'project', 'location', 'machine', 'type', 'last_maintenance_log')
    inlines = [CommentInline, TestLogInline]

    def project(self, obj):
        try:
            return Device.objects.get(pk=obj.pk).machine.location.project.project_name
        except AttributeError:
            return '-'

    def location(self, obj):
        try:
            return Device.objects.get(pk=obj.pk).machine.location.name
        except AttributeError:
            return '-'

    def last_maintenance_log(self, obj):
        try:
            log = AdminLog.objects.filter(object_id=obj.pk).latest('time')
            return '{} | {}'.format(log.time.strftime("%d/%m/%Y, %-I:%M %p"), log.title)
        except AttributeError:
            return '-' 

所有MachineLocationProject 都是数据库中的表。

在查看django-debug-toolbar 中的查询后,我发现了一些可怕的事情。 该屏幕需要 287 个 sql 查询!是的,超过两百!

我能做些什么来减少这个时间到合理的程度吗?

编辑: 感谢布鲁诺,我删除了Device.objects.get(pk=obj.id)(因为它真的是多余的,我完全忽略了这一点。

所以我在任何地方都放了obj.,例如obj.machine.location.project.project_name

仅此一项就将速度和查询次数降低了一半,到目前为止还不错。

融合obj 方法和select_related 方法我没有问题。这是我当前的代码,比仅使用obj 方法更糟糕。

def project(self, obj):
        try:
            Device.objects.select_related('machine__location__project').get(id=obj.pk).machine.location.project.project_name
        except AttributeError:
            return '-'

它在查询中创建了一个很好的 INNER JOIN,但性能比没有它并且只使用 obj.machine.location.project.project_name 时差大约 15%

我做错了什么?

EDIT2:

我得到的最佳性能是使用以下代码:

@admin.register(Device)
class DeviceAdmin(admin.ModelAdmin):
    list_select_related = True
    save_as = True
    form = DeviceForm
    list_display = ('id', 'name', 'project', 'location', 'machine', 'type', 'last_maintenance_log')
    inlines = [CommentInline, TestLogInline]

    def project(self, obj):
        try:
            return obj.machine.location.project.project_name
        except AttributeError:
            return '-'

    def location(self, obj):
        try:
            return obj.machine.location.name
        except AttributeError:
            return '-'

    def last_maintenance_log(self, obj):
        try:
            log = AdminLog.objects.filter(object_id=obj.pk).latest('time')
            return '{} | {}'.format(log.time.strftime("%d/%m/%Y, %-I:%M %p"), log.title)
        except AttributeError:
            return '-'

    def get_queryset(self, request):
        return Device.objects.select_related('machine__location__project').all()

这将查询次数从近 300 个减少到 104 个,时间减少了 50% 以上。可以进一步改进吗?

【问题讨论】:

  • 查询的数量不足为奇。据我所知,您每行做了 3 次额外查询来获取方法的数据。
  • 嗯,是的,这种方法并不令人惊讶,但我想知道这是否可以避免,我想保留这些额外的信息,但成本太高了。

标签: python django django-rest-framework django-admin


【解决方案1】:

首先,避免完全无用的查询 - 这个:

Device.objects.get(pk=obj.pk)

由于obj 已经您正在寻找的Device 实例,所以已经无用了。

然后override your DeviceAdmin.get_queryset method 正确使用select_relatedprefetch_related,这样您就只有所需的最少查询数。

【讨论】:

  • 您好 Bruno,感谢您的意见,我现在就试试这个并报告结果!
  • 仅仅通过省略无用的查询,我设法将查询计数减少到一半 (125),继续使用 select_related 和 prefetch_related,尽管它不像我想象的那么清楚。
  • @JosefKorbel 你可能想读这个:stackoverflow.com/questions/31237042/…
  • 不错的阅读,我确实理解了它,但它没有帮助,事实上更糟。你不介意阅读我编辑的问题吗?
  • 看起来你的最后一次尝试变得更好了。
猜你喜欢
  • 2013-02-04
  • 2019-07-13
  • 2021-04-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多