【发布时间】: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 '-'
所有Machine、Location 和Project 都是数据库中的表。
在查看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