用Django ListView中的具有isull = False和order_by的外键解决慢速查询

问题描述 投票:0回答:1

我有一个Django ListView,可以通过“活跃的”人员进行分页。

(简化的)模型:

class Person(models.Model):
    name = models.CharField()
    # ...
    active_schedule = models.ForeignKey('Schedule', related_name='+', null=True, on_delete=models.SET_NULL)

class Schedule(models.Model):
    field = models.PositiveIntegerField(default=0)
    # ...
    person = models.ForeignKey(Person, related_name='schedules', on_delete=models.CASCADE)

Person表包含近700.000行,Schedule表包含刚超过2.000.000行(平均每个Person拥有2-3 Schedule记录,尽管许多记录都没有,很多都更多)。对于“活动”人员,设置为active_schedule外键,任何时候大约有5.000。

ListView应该显示所有活动人员,并按计划中的field排序(在某些情况下,这种情况似乎无关紧要。)

查询将变为:

Person.objects
    .filter(active_schedule__isnull=False)
    .select_related('active_schedule')
    .order_by('active_schedule__field')

特别是相关字段上的order_by使此查询非常慢(也就是说:大约需要一秒钟,对于Web应用程序来说太慢了。)

我希望filter条件能够选择5000条记录,然后使它们变得相对容易排序。但是,当我对此查询运行explain时,它表明(Postgres)数据库正在弄乱更多行:

Gather Merge  (cost=224316.51..290280.48 rows=565366 width=227)
  Workers Planned: 2
  ->  Sort  (cost=223316.49..224023.19 rows=282683 width=227)
        Sort Key: exampledb_schedule.field
        ->  Parallel Hash Join  (cost=89795.12..135883.20 rows=282683 width=227)
              Hash Cond: (exampledb_person.active_schedule_id = exampledb_schedule.id)
              ->  Parallel Seq Scan on exampledb_person  (cost=0.00..21263.03 rows=282683 width=161)
                    Filter: (active_schedule_id IS NOT NULL)
              ->  Parallel Hash  (cost=67411.27..67411.27 rows=924228 width=66)
                    ->  Parallel Seq Scan on exampledb_schedule  (cost=0.00..67411.27 rows=924228 width=66)

我最近将模型更改为这种方式。在以前的版本中,我有一个模型,其中只有〜5.000个活跃人。在这个小桌子上执行order_by相当快!我希望与当前模型达到相同的速度。

我尝试仅检索Listview所需的字段(使用values),但这确实有帮助,但效果不大。我还尝试在active_schedule上设置related_name并从Schedule处理问题,但这没什么区别。我尝试将db_index放在Schedule.field上,但这似乎只会使速度变慢。条件查询也无济于事(尽管我可能没有尝试所有可能性)。我很茫然。

ORM查询生成的SQL语句:

SELECT 
    "exampledb_person"."id", 
    "exampledb_person"."name", 
    ...
    "exampledb_person"."active_schedule_id", 
    "exampledb_person"."created", 
    "exampledb_person"."updated", 
    "exampledb_schedule"."id", 
    "exampledb_schedule"."person_id", 
    "exampledb_schedule"."field", 
    ...
    "exampledb_schedule"."created", 
    "exampledb_schedule"."updated" 
FROM 
    "exampledb_person" 
INNER JOIN 
    "exampledb_schedule" 
ON ("exampledb_person"."active_schedule_id" = "exampledb_schedule"."id") 
WHERE 
    "exampledb_person"."active_schedule_id" IS NOT NULL 
ORDER BY 
    "exampledb_schedule"."field" ASC

(为简单起见,省略了某些字段。)

是否可以加快此查询的速度,或者我应该恢复为活动人员使用特殊的模型?

EDIT:当我更改查询(仅用于比较/测试)以对Person上未索引的字段进行排序时,查询同样显示。但是,如果我随后向该字段添加索引,则查询速度很快!我必须尝试一下,因为SQL语句确实显示它在"exampledb_schedule"."field"上排序-没有索引的字段,但是就像我说的:在字段上添加索引没有区别。

EDIT:我想还值得注意的是,当直接在Schedule上尝试更简单的排序查询时,无论是否在索引字段上,它的速度都更快。例如,对于此测试,我已向Schedule.field添加了索引,然后以下查询快速运行:

Schedule.objects.order_by('field')

解决方案在这里某处……

django orm foreign-keys
1个回答
0
投票

@ guarav的评论和我的修改将我引向了解决方案的方向,这种解决方案在我的脸上凝视了一段时间……

我的问题中的filter子句-filter(active_schedule__isnull=False)-似乎使数据库索引无效。我当时不知道这一点,并希望数据库专家会指出我的方向。

解决方案是根据Schedule.field进行过滤,对于无效的“个人”记录,该值为0,对于有效的“个人”记录为> 0:

Person.objects
    .select_related('active_schedule')
    .filter(active_schedule__field__gte=1)
    .order_by('active_schedule__field')

此查询正确使用索引并且速度很快(20ms相对于〜1000ms)。

© www.soinside.com 2019 - 2024. All rights reserved.