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: TemplateDoesNotExist at /admin/plans/plan/