Django 不在排序时使用索引
Django does not use index on ordering
我正在尝试优化我在预取中使用的 Django(版本 1.11)中的查询。所以我有一个与工资有关系的 StaffProfile 模型:
class Wages(Model):
# ...
staff = models.ForeignKey('StaffProfile', on_delete=models.CASCADE, null=True)
title = models.CharField(_('Title'), max_length=50)
class StaffProfile(Model):
# ...
objects = StaffProfileManager.from_queryset(StaffQuerySet)()
StaffQuerySet
有一个专门的方法:
class StaffQuerySet(QuerySet):
def my_prefetch(
) -> QuerySet:
return self.prefetch_related(
Prefetch(
'wages_set',
queryset=Wages.objects.filter(
# ...
).order_by(
Upper('title')
),
to_attr='prefetched_wages'
),
)
我注意到,当我 运行 这个查询时:Wages.objects.all().order_by(Upper('title'))
我的数据库 (PostgreSQL) 显然对结果进行了排序(下面是 EXPLAIN [=32 的输出=] 在生成的 SQL):
Sort (cost=21.31..21.86 rows=220 width=166)
Sort Key: (upper((title)::text))
-> Seq Scan on staff_wages (cost=0.00..12.75 rows=220 width=166)
生成的查询是:
In [25]: str(Wages.objects.all().order_by(Upper('title')).only('title').query)
Out[25]: 'SELECT "staff_wages"."uuid", "staff_wages"."title" FROM "staff_wages" ORDER BY UPPER("staff_wages"."title") ASC'
所以我尝试在 title
字段上设置索引。为了实现这一点,我创建了一个迁移:
class Migration(migrations.Migration):
dependencies = [
('staff', '0020_merge_20201116_0929'),
]
operations = [
migrations.RunSQL(
sql='CREATE INDEX "staff_wages_title_upper_idx" ON "staff_wages" (UPPER("title"));',
reverse_sql='DROP INDEX "staff_wages_title_upper_idx";'
)
]
我已经 运行 它并且我看到它是在数据库中创建的。但是,当我现在 运行 我的查询时, EXPLAIN
给出完全相同的输出,而我希望它使用我的索引。我究竟做错了什么?我应该使用与 b 树不同的索引吗?还是无法达到我想要的?
即使你创建了一个索引来支持排序,PostgreSQL 如果认为排序更便宜,也可能会选择不使用它。
如果 PostgreSQL 不选择索引是因为它不能或因为它认为排序会更好,要决定这个问题,你可以暂时不鼓励排序:
SET enable_sort = off;
这并没有完全禁用排序,而是对其施加了天文数字的成本惩罚,因此优化器将尽可能选择不同的计划。
然后EXPLAIN
再次查询。如果你的索引还没有被使用,它就不能被使用。如果使用索引,则将估计成本与计划成本进行比较并排序。它应该更低。
您可以使用 EXPLAIN (ANALYZE)
实际执行查询并显示执行时间和估计值。这样你就可以检查优化器是否做出了正确的选择。如果不是,并且索引扫描速度更快,则可能是参数 random_page_cost
或 effective_cache_size
未设置为正确描述您的系统。
我正在尝试优化我在预取中使用的 Django(版本 1.11)中的查询。所以我有一个与工资有关系的 StaffProfile 模型:
class Wages(Model):
# ...
staff = models.ForeignKey('StaffProfile', on_delete=models.CASCADE, null=True)
title = models.CharField(_('Title'), max_length=50)
class StaffProfile(Model):
# ...
objects = StaffProfileManager.from_queryset(StaffQuerySet)()
StaffQuerySet
有一个专门的方法:
class StaffQuerySet(QuerySet):
def my_prefetch(
) -> QuerySet:
return self.prefetch_related(
Prefetch(
'wages_set',
queryset=Wages.objects.filter(
# ...
).order_by(
Upper('title')
),
to_attr='prefetched_wages'
),
)
我注意到,当我 运行 这个查询时:Wages.objects.all().order_by(Upper('title'))
我的数据库 (PostgreSQL) 显然对结果进行了排序(下面是 EXPLAIN [=32 的输出=] 在生成的 SQL):
Sort (cost=21.31..21.86 rows=220 width=166)
Sort Key: (upper((title)::text))
-> Seq Scan on staff_wages (cost=0.00..12.75 rows=220 width=166)
生成的查询是:
In [25]: str(Wages.objects.all().order_by(Upper('title')).only('title').query)
Out[25]: 'SELECT "staff_wages"."uuid", "staff_wages"."title" FROM "staff_wages" ORDER BY UPPER("staff_wages"."title") ASC'
所以我尝试在 title
字段上设置索引。为了实现这一点,我创建了一个迁移:
class Migration(migrations.Migration):
dependencies = [
('staff', '0020_merge_20201116_0929'),
]
operations = [
migrations.RunSQL(
sql='CREATE INDEX "staff_wages_title_upper_idx" ON "staff_wages" (UPPER("title"));',
reverse_sql='DROP INDEX "staff_wages_title_upper_idx";'
)
]
我已经 运行 它并且我看到它是在数据库中创建的。但是,当我现在 运行 我的查询时, EXPLAIN
给出完全相同的输出,而我希望它使用我的索引。我究竟做错了什么?我应该使用与 b 树不同的索引吗?还是无法达到我想要的?
即使你创建了一个索引来支持排序,PostgreSQL 如果认为排序更便宜,也可能会选择不使用它。
如果 PostgreSQL 不选择索引是因为它不能或因为它认为排序会更好,要决定这个问题,你可以暂时不鼓励排序:
SET enable_sort = off;
这并没有完全禁用排序,而是对其施加了天文数字的成本惩罚,因此优化器将尽可能选择不同的计划。
然后EXPLAIN
再次查询。如果你的索引还没有被使用,它就不能被使用。如果使用索引,则将估计成本与计划成本进行比较并排序。它应该更低。
您可以使用 EXPLAIN (ANALYZE)
实际执行查询并显示执行时间和估计值。这样你就可以检查优化器是否做出了正确的选择。如果不是,并且索引扫描速度更快,则可能是参数 random_page_cost
或 effective_cache_size
未设置为正确描述您的系统。