Django管理MySQL慢INNER JOIN

时间:2013-04-11 21:22:14

标签: mysql django django-admin

我有一个包含3个ForeignKey字段的简单模型。

class Car(models.Model):
    wheel = models.ForeignKey('Wheel', related_name='wheels')
    created = models.DateTimeField(auto_now_add=True)
    max_speed = models.PositiveSmallIntegerField(null=True)
    dealer = models.ForeignKey('Dealer')
    category = models.ForeignKey('Category')

对于django admin中的列表视图,我得到4个查询。其中一个是带有3个INNER JOINS的SELECT。那个查询是慢的方法。用STRAIGHT_JOIN替换INNER JOIN将解决问题。有没有办法在评估之前修补管理生成的查询?

6 个答案:

答案 0 :(得分:4)

我已经为Django ORM实现了INNER JOIN的修复,如果使用INNER JOIN进行订购,它将使用STRAIGHT_JOIN。我和Django核心开发者谈过,我们决定暂时将其作为一个单独的后端。所以你可以在这里查看:https://pypi.python.org/pypi/django-mysql-fix

但是,还有另外一种解决方法。使用James的答案中的片段,但将select_related替换为:

qs = qs.select_related('').prefetch_related('wheel', 'dealer', 'category')

它将取消INNER JOIN并使用4个单独的查询:1用于获取汽车,另外3个用car_id IN(...)。

<强>更新 我找到了另外一个解决方法。在ForeignKey字段中指定null = True后,Django将使用LEFT OUTER JOIN而不是INNER JOIN。在这种情况下,LEFT OUTER JOIN在没有性能问题的情况下工作,但您可能还面临其他我尚未发现的问题。

答案 1 :(得分:3)

您可以指定list_select_related = ()以防止django使用内部联接:

class CarAdmin(admin.ModelAdmin):
    list_select_related = ()

答案 2 :(得分:2)

你可以覆盖

  def changelist_view(self, request, extra_context=None):
管理类中的

方法继承自ModelAdmin

像这样的东西(但这个问题相当陈旧): Django Admin: Getting a QuerySet filtered according to GET string, exactly as seen in the change list?

答案 3 :(得分:1)

好的,我找到了修补管理员生成的查询的方法。这很丑,但似乎有用

class CarChangeList(ChangeList):

    def get_results(self, request):
        """Override to patch ORM generated SQL"""
        super(CarChangeList, self).get_results(request)
        original_qs = self.result_list
        sql = str(original_qs.query)
        new_qs = Car.objects.raw(sql.replace('INNER JOIN', 'STRAIGHT_JOIN'))

        def patch_len(self):
           return original_qs.count()
        new_qs.__class__.__len__ = patch_len

        self.result_list = new_qs


class CarAdmin(admin.ModelAdmin):

    list_display = ('wheel', 'max_speed', 'dealer', 'category', 'created')

    def get_changelist(self, request, **kwargs):
        """Return custom Changelist"""
        return CarChangeList

admin.site.register(Rank, RankAdmin)

答案 4 :(得分:1)

我在Django管理员(版本1.4.9)中遇到了同样的问题,当MySQL支持时,相当简单的管理员列表页面非常慢。

在我的情况下,如果ChangeList.get_query_set()中的任何字段很多,则select_related()方法会向查询集添加过于宽泛的全局list_display一对一的关系。对于一个合适的数据库(咳嗽PostgreSQL咳嗽),这不会是一个问题,但是对于MySQL而言,只需要通过这种方式触发一些连接。

我发现最干净的解决方案是将全局select_related()指令替换为更有针对性的指令,该指令仅连接真正必要的表。通过使用显式关系名称调用select_related(),这很容易做到。

这种方法可能最终会为多个后续查询交换数据库内连接,但如果MySQL在大型查询中遇到窒息,那么很多小的可能会更快。

这就是我所做的,或多或少:

from django.contrib.admin.views.main import ChangeList


class CarChangeList(ChangeList):

    def get_query_set(self, request):
        """
        Replace a global select_related() directive added by Django in 
        ChangeList.get_query_set() with a more limited one.
        """
        qs = super(CarChangeList, self).get_query_set(request)
        qs = qs.select_related('wheel')  # Don't join on dealer or category
        return qs


class CarAdmin(admin.ModelAdmin):

        def get_changelist(self, request, **kwargs):
            return CarChangeList

答案 5 :(得分:1)

我对MySQL的管理查询速度很慢,发现最简单的解决方案是将STRAIGHT_JOIN添加到查询中。我想出了一种方法,可以将其添加到QuerySet,而不是被迫转到.raw(),后者无法与管理员合作,并将其作为{{3的一部分}}。然后你可以:

def get_queryset(self, request):
    qs = super(MyAdmin, self).get_queryset(request)
    return qs.straight_join()