django admin 错误地将 order by 添加到查询中

Posted

技术标签:

【中文标题】django admin 错误地将 order by 添加到查询中【英文标题】:django admin incorrectly adds order by into query 【发布时间】:2015-12-01 20:37:57 【问题描述】:

感谢 django 调试工具栏,我注意到每个 django 管理列表页面总是在我的所有查询中添加一个“ORDER BY id DESC”,即使我手动覆盖了 admin.ModelAdmin 的 get_queryset 方法(我通常这样做是因为我想在我的一些管理页面上进行自定义排序)

我想这并不是真正值得担心的事情,但这是数据库需要执行的额外排序操作,即使它根本没有意义。

有什么办法可以防止这种情况发生吗?似乎在某些模型上(即使不是全部),如果我添加排序元数据,那么它不会自动按 id 添加订单,但它会按该字段添加,这也是我不这样做的'不想要,因为这样做会将该顺序添加到我在代码中的所有其他查询中。

编辑:似乎罪魁祸首在 ChangeList 的 django.contrib.admin.views.main 上,在第 316 行的函数 get_ordering 上(django 1.7.10)

 pk_name = self.lookup_opts.pk.name
    if not (set(ordering) & set(['pk', '-pk', pk_name, '-' + pk_name])):
        # The two sets do not intersect, meaning the pk isn't present. So
        # we add it.
        ordering.append('-pk')

我想知道这背后的原因是什么......

编辑: 为了提高性能,并且由于 mysql(和 InnoDB)在没有给出 order by 时以聚集索引顺序返回数据,我可以安全地删除附加的 id。 这样做很简单,我刚刚扩展了 django 的 ChangeList 并修改了 get_ordering 方法。之后,只需创建一个自定义的管理模型,该模型从 ModelAdmin 扩展并覆盖 get_changelist 方法以返回上述类。

我希望它可以帮助任何人:)

【问题讨论】:

从技术上讲,任何没有ORDER BY 的 SQL 查询在结果顺序方面都受 RDMBS 的影响,因为根据定义,无序的结果是无序的……但 MySQL 倾向于以某种可预测的方式排序结果;然而,可预测性和确定性并不等同。这可能是在未指定排序时保持结果确定性的尝试,但我在推测。 我想排序需要能够对结果进行分页,但是,如果根本没有提供 odering 是有道理的,如果已经设置了排序,我认为 django 没有理由再添加另一个订购到最后 如果没有排序则不添加pk字段,如果不存在则添加。这正在扼杀我在具有巨大表的 MySQL 数据库上的性能,其中仅按我想要的字段排序非常快,但是在 order by 中添加额外的 pk 会使它慢 4 倍,因为 mysql 必须获取所有数据然后排序. 我怀疑他们为什么会这样做,因为非唯一列的排序在理论上仍然是模棱两可的(你如何定义具有相同值的多行的顺序?折腾PK)...实际上,在大多数情况下这不应该受到伤害,因为 PK 是免费的——它总是被复制到 MySQL 中的索引行中(呃,至少在 InnoDB 中)——但在其他情况下,特别是在选择标准和排序不能同时使用相同索引的情况下,它可能会扭曲优化器对查询计划的选择。 查询变得非常慢,基本上是按连接字段排序的(有一个巨大的表与一个较小的表以 1-N 关系连接,因为巨大的表需要关系中的一个字段),连接字段被索引并且在where条件下,因此仅按此字段排序会使查询非常快,但是在顺序中添加额外的'id'只会杀死查询性能。唯一的选择是使用子查询来模拟后期行查找,但这会破坏 django 管理分页,或者无法使用它 【参考方案1】:

遇到了与这个问题完全相同的问题,当我已经有唯一的排序时,由于 ID 排序,管理员查询集慢了 4 倍。感谢@user1777914 和他的工作,我没有其他负载超时!如果其他人遭受同样的痛苦,我只是在这里添加这个答案。正如 user1777914 提到的扩展 ChangeList:

class NoPkChangeList(ChangeList):
    def get_ordering(self, request, queryset):
        """
        Returns the list of ordering fields for the change list.
        First we check the get_ordering() method in model admin, then we check
        the object's default ordering. Then, any manually-specified ordering
        from the query string overrides anything. Finally, WE REMOVE the primary
        key ordering field.
        """
        params = self.params
        ordering = list(self.model_admin.get_ordering(request) or self._get_default_ordering())
        if ORDER_VAR in params:
            # Clear ordering and used params
            ordering = []
            order_params = params[ORDER_VAR].split('.')
            for p in order_params:
                try:
                    none, pfx, idx = p.rpartition('-')
                    field_name = self.list_display[int(idx)]
                    order_field = self.get_ordering_field(field_name)
                    if not order_field:
                        continue  # No 'admin_order_field', skip it
                    # reverse order if order_field has already "-" as prefix
                    if order_field.startswith('-') and pfx == "-":
                        ordering.append(order_field[1:])
                    else:
                        ordering.append(pfx + order_field)
                except (IndexError, ValueError):
                    continue  # Invalid ordering specified, skip it.

        # Add the given query's ordering fields, if any.
        ordering.extend(queryset.query.order_by)

        # Ensure that the primary key is systematically present in the list of
        # ordering fields so we can guarantee a deterministic order across all
        # database backends.
        # pk_name = self.lookup_opts.pk.name
        # if not (set(ordering) & 'pk', '-pk', pk_name, '-' + pk_name):
        #     # The two sets do not intersect, meaning the pk isn't present. So
        #     # we add it.
        #     ordering.append('-pk')

        return ordering

然后在你的 ModelAdmin 中覆盖 get_changelist:

class MyAdmin(ModelAdmin):
    def get_changelist(self, request, **kwargs):
        return NoPkChangeList

【讨论】:

【参考方案2】:

7Wonders的答案可以简化为以下语句,因为只有ChangeList._get_deterministic_ordering()需要改变:

# admin.py
class MyAdmin(ModelAdmin):
    def get_changelist(self, request, **kwargs):
        """Improve changelist query speed by disabling deterministic ordering.

        Please be aware that this might disturb pagination.
        """
        from django.contrib.admin.views.main import ChangeList

        class NoDeterministicOrderChangeList(ChangeList):
            def _get_deterministic_ordering(self, ordering):
                return ordering

        return NoDeterministicOrderChangeList 

【讨论】:

以上是关于django admin 错误地将 order by 添加到查询中的主要内容,如果未能解决你的问题,请参考以下文章

Django Admin 中的内联

Django-admin按多个字段排序

Django-admin: TemplateDoesNotExist at /admin/plans/plan/

Django 错误:关系 django_admin_log 的权限被拒绝

已在 /admin/ 处注册 Django 1.2 错误

Django admin:如何在单击自定义按钮时调用操作?