Django后台list_display在外键时异常慢

2 投票
4 回答
4947 浏览
提问于 2025-04-16 12:55

Django 1.2.5
Python: 2.5.5

我在管理一个体育模型的列表时,发现速度变得非常慢(400条记录要花5分钟)。在我们有400场比赛、50多支队伍和2项运动之前,这个列表的加载速度大约只需要一秒钟。

我用一种很糟糕的方法解决了这个问题,所以想看看有没有人遇到过类似的情况。我的应用程序结构是这样的:

models:

Sport( models.Model )
    name

Venue( models.Model )
    name

Team( models.Model )
    name

Fixture( models.Model )
    date
    sport = models.ForeignKey(Sport)
    venue = models.ForeignKey(Venue)

TeamFixture( Fixture )
    team1 = models.ForeignKey(Team, related_name="Team 1")
    team2 = models.ForeignKey(Team, related_name="Team 2")


admin:

TeamFixture_ModelAdmin (ModelAdmin)
    list_display = ('date','sport','venue','team1','team2',)

如果我从列表显示中移除任何外键,加载速度就会很快。但只要我添加任何外键,速度就会变慢。

我通过使用非外键的方式解决了这个问题,具体是通过在模型初始化时计算这些值,所以现在这样做是有效的:

models:

TeamFixture( Fixture )
    team1 = models.ForeignKey(Team, related_name="Team 1")
    team2 = models.ForeignKey(Team, related_name="Team 2")
    sport_name = ""
    venue_name = ""
    team1_name = ""
    team2_name = ""

    def __init__(self, *args, **kwargs):
        super(TeamFixture, self).__init__(*args, **kwargs)

        self.sport_name = self.sport.name
        self.venue_name = self.venue.name
        self.team1_name = self.team1.name
        self.team2_name = self.team2.name

admin:

TeamFixture_ModelAdmin (ModelAdmin)
    list_display = ('date','sport_name','venue_name','team1_name','team2_name',)

其他模型的管理界面目前都很好,即使有几千条记录,所有在实际网站上的视图也都运行正常。

4 个回答

0

这是一个关于Django后台和外键的老问题。当你尝试加载一个对象时,它会试图获取所有与这个外键相关的对象。举个例子,如果你要加载一个包含大约100个团队的数据,这个过程就会一次性把这100个团队都加载进来。为了优化这个过程,你可以使用一种叫做raw_fields的东西。这样做的好处是,它不会一次性调用所有的对象,而是限制调用的次数,只有在触发某个事件时(比如你选择一个团队时)才会进行调用。

如果这样看起来有点乱,你可以尝试使用这个类:

"""
For Raw_id_field to optimize django performance for many to many fields
"""
class RawIdWidget(ManyToManyRawIdWidget):
    def label_for_value(self, value):
        values = value.split(',')
        str_values = []
        key = self.rel.get_related_field().name
        for v in values:
            try:
                obj = self.rel.to._default_manager.using(self.db).get(**{key: v})
                x = smart_unicode(obj)
                change_url = reverse(
                    "admin:%s_%s_change" % (obj._meta.app_label, obj._meta.object_name.lower()),
                    args=(obj.pk,)
                )
                str_values += ['<strong><a href="%s">%s</a></strong>' % (change_url, escape(x))]
            except self.rel.to.DoesNotExist:
                str_values += [u'No input or index in the db']
        return u', '.join(str_values)

class ImproveRawId(admin.ModelAdmin):
    raw_id_fields = ('created_by', 'updated_by')
    def formfield_for_dbfield(self, db_field, **kwargs):
        if db_field.name in self.raw_id_fields:
            kwargs.pop("request", None)
            type = db_field.rel.__class__.__name__
            kwargs['widget'] = RawIdWidget(db_field.rel, site)
            return db_field.formfield(**kwargs)
        return super(ImproveRawId, self).formfield_for_dbfield(db_field, **kwargs)

只要确保你正确继承这个类。我猜可能是像TeamFixture_ModelAdmin (ImproveRawIdFieldsForm)这样的形式。这样做很可能会让你的Django后台性能大幅提升。

3

我首先会关注数据库的调用。如果你还没这样做,建议安装一下 django-debug-toolbar。这个工具非常好用,可以让你查看当前请求所执行的所有 SQL 查询。我猜这些查询会很多。如果你查看这些查询,就能找到问题所在。

我自己遇到过一个问题:当一个模型的 __unicode__ 方法使用了外键时,每个实例都会导致一次数据库查询。我知道有两种方法可以解决这个问题:

  • 使用 select_related,这通常是最好的选择。
  • 让你的 __unicode__ 方法返回一个静态字符串,并重写 save 方法来相应地更新这个字符串。
4

这让我很头疼。虽然我把list_select_related设置为True,但在admin界面中,如果在list_display里加了一个指向User的外键,系统还是会对每一行数据发起一个查询,这样就导致列表加载得很慢。明明设置了select_related为True,按理说Django admin不应该对每一行都发起这样的查询。到底发生了什么呢?

撰写回答