Проблемы с производительностью при использовании Min('id') Django в большой таблице

У меня есть модель Table, которая получает новую запись каждые несколько секунд, так что это довольно большая таблица.

class Table(models.Model):
    id: int
    pk: int

    timestamp = models.PositiveBigIntegerField(db_index=True)

Теперь я пытаюсь получить первую запись за последние 24 часа. И по какой-то причине это занимает слишком много времени. Как будто мы говорим о секундах. Но если я пытаюсь получить последнюю, я получаю ее мгновенно. Я перепробовал следующие запросы:

from_ts = int(time.time()) - 24 * 60 * 60
first_entry = Table.objects.filter(timestamp__gte=from_ts).first()
first_entry = Table.objects.filter(timestamp__gte=from_ts).aggregate(Min('id'))

Выполнение обоих занимает несколько секунд, но если я попробую .aggregate(Min('timestamp')), то результат будет получен почти мгновенно. Я попробовал .filter(timestamp__gte=from_ts)[0], который работает быстро и действительно работает, но я знаю его неопределенное поведение, потому что записи не сортируются, и в некоторых крайних случаях это может не сработать. Затем я попытался просто получить весь запрос на python и найти min на python, и это произошло почти мгновенно.

first_entry = min(list(Table.objects.filter(timestamp__gte=from_ts)))

Кто-нибудь может объяснить, что именно здесь происходит и какое решение является самым чистым и оптимальным?

Ваши наборы запросов фильтруются по временной метке, а также упорядочиваются по идентификатору. Хотя может показаться, что первый запрос не выполняет этого, но .first() неявно выполняет и order_by для идентификатора, если порядок еще не указан.

Хотя у вас есть отдельные индексы для идентификатора и временной метки, скорее всего, проблема в том, что база данных не рассматривает ни один из них, подходящий для вашего запроса, что приводит к неэффективности (я не очень разбираюсь в базах данных, поэтому не лучший специалист по индексам).

Чтобы сделать запрос эффективным с использованием индекса, который у вас есть на данный момент, вы можете упорядочить временную метку и получить первый объект в соответствии с этим порядком (который в идеале также должен быть первым идентификатором, если ваша таблица хранит данные таким образом):

first_entry = Table.objects.filter(timestamp__gte=from_ts).order_by("timestamp").first()

Просто добавьте индекс к временной метке и идентификатору:

class Table(models.Model):
    timestamp = models.PositiveBigIntegerField(db_index=True)

    class Meta:
        indexes = [
            models.Index(fields=['timestamp', 'id']),
        ]

Затем используйте этот запрос, чтобы получить первую запись за последние 24 часа.

first_entry = Table.objects.filter(timestamp__gte=from_ts).order_by('id').first()

Это будет намного быстрее, поскольку база данных может использовать объединенный индекс как для фильтрации, так и для сортировки, не просматривая всю таблицу целиком.

@ Абдул Азиз Баркат ответил на вопрос. Я просто хочу добавить немного дополнительного контекста.

Базы данных используют индексы для фильтрации и сортировки, но их также можно использовать для извлечения данных, если другие данные не требуются. Действительно, вы указали индекс базы данных в поле timestamp. Это означает, что если вы делаете запросы по временной метке, база данных может использовать это для эффективного определения того, какие записи находятся в пределах заданного диапазона, без необходимости просматривать все эти записи.

Если вы хотите дополнительно выполнить сортировку по ним, он может отсортировать записи "заранее", не извлекая их, поскольку он знает, что наименьшая временная метка предназначена для временных меток, соответствующих фильтру: для этого он может использовать индекс. Оба запроса не требуют извлечения данных из записей.

Если вы теперь хотите получить наименьшее значение id, у базы данных нет другого выбора, кроме как начать выборку всех записей, удовлетворяющих условию фильтрации, и посмотреть на идентификаторы. Извлечение записей обычно обходится намного дороже: для этого требуется доступ к хранилищу базы данных, часто эти записи даже не "упакованы" вместе в одном месте, что может привести к считыванию большого количества данных в память.

Однако, если вы хотите получить первую запись по временной метке, необязательно извлекать все данные. Действительно, если вы сделаете запрос:

SELECT id
FROM my_table
WHERE timestamp >= 2025-06-01
ORDER BY timestamp
LIMIT 1

он может использовать индекс для определения единственной записи, которую он должен извлечь, и извлекать ее с помощью одной небольшой выборки.

Вернуться на верх