У меня есть 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 строк, а таблица «Расписание» содержит чуть более 2 000 000 строк (в среднем на каждого сотрудника приходится 2-3 записи в расписании, хотя у многих их нет, а у многих больше). Для «активного» Персона установлен active_schedule
ForeignKey, из которых в любой момент времени может быть около 5.000.
Предполагается, что ListView отображает все активные персоны, отсортированные по field
в расписании (и некоторые другие условия, которые, кажется, не имеют значения для этого случая).
Запрос затем становится:
Person.objects
.filter(active_schedule__isnull=False)
.select_related('active_schedule')
.order_by('active_schedule__field')
В частности, order_by в связанном поле делает этот запрос ужасно медленным (что is: это занимает около секунды, что слишком медленно для веб-приложения).
Я надеялся, что условие filter
выберет 5000 записей, которые затем станут относительно легко сортируемыми. Но когда я запускаю объяснение для этого запроса, он показывает, что база данных (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)
Я недавно изменил модели, чтобы они были такими. В предыдущей версии у меня была модель с ~ 5000 активными людьми. Выполнение order_by на этом маленьком столе было значительно быстрее! Я надеюсь достичь той же скорости с текущими моделями.
Я попытался получить только те поля, которые необходимы для Listview (используя values
), что немного помогает, но не сильно. Я также попытался установить значение related_name на active_schedule
и подойти к проблеме из расписания, но это не имеет значения. Я попытался поставить db_index
на Schedule.field
, но это, кажется, только делает вещи медленнее. Условные запросы также не помогли (хотя я, вероятно, не пробовал все возможности). Я в растерянности.
Оператор SQL, сгенерированный запросом ORM:
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
(Некоторые поля были опущены для простоты.)
Можно ли ускорить этот запрос, или я должен вернуться к использованию специальной модели для активного человека?
РЕДАКТИРОВАТЬ: Когда я изменяю запрос, только для сравнения / тестирования, для сортировки по полю без индекса на Person
запрос одинаково показан. Однако, если я добавлю индекс к этому полю, запрос будет быстрым! Мне пришлось попробовать это, поскольку оператор SQL действительно показывает, что он упорядочен по "exampledb_schedule"."field"
- поле без индекса, но, как я уже сказал: добавление индекса к полю не имеет значения.
РЕДАКТИРОВАТЬ: Полагаю, стоит также отметить, что при попытке выполнить более простой запрос сортировки непосредственно по расписанию, либо по индексируемому полю, либо нет, это НАМНОГО быстрее. Например, для этого теста я добавил индекс к Schedule.field
, а затем быстро выполняется следующий запрос:
Schedule.objects.order_by('field')
Где-то здесь лежит решение ...