Фильтр Джанго. Что более эффективно — .filter(диапазон дат__) или .all() и фильтровать с помощью Python

#python #mysql #django #database #memory-management

Вопрос:

У меня довольно большая таблица базы данных (1 м строк) , которая испытывает проблемы при фильтрации результатов в Django.

В настоящее время логика фильтра выглядит следующим образом:

 results = Result.objects.filter(date__range=(date_from, date_to))
for result in results:
    # do stuff
 

В некоторые периоды это вызывает сбой (вероятно, из-за истощения памяти).

Мне интересно, было бы более эффективно заменить его следующим:

 results = Result.objects.all().order_by('-id')
for result in results:
    if result.date > date_to:
        continue
    if result.date < date_from:
        break
    # do stuff
 

Теоретически, поскольку .all() создает ленивый набор запросов, он может работать лучше, чем фильтрация диапазонов в базе данных с 1M строками.
Также было бы интересно узнать о потреблении памяти в обоих случаях.

Может быть, есть другое решение, как сделать это более эффективно и в постоянной памяти?

Спасибо!

Ответ №1:

Теоретически, поскольку .all() создается ленивый набор запросов, он может работать лучше, чем фильтрация диапазонов в базе данных с 1M строками. Также было бы интересно узнать о потреблении памяти в обоих случаях.

Набор запросов ленив в том смысле, что он будет извлекать объекты только в случае необходимости, но как только ему придется это сделать, он получит все элементы. .all() Кстати, ленив не только ленив, все QuerySet s ленивы, поэтому, если вы определяете Result.objects.filter(…) набор запросов, это QuerySet тоже лениво.

Но независимо от того, как это реализовано, фильтрация на стороне базы данных более эффективна, поскольку базы данных предназначены для этого, и это приводит к меньшей пропускной способности от базы данных до уровня Python/Django.

В случае возникновения проблем с памятью это, скорее всего , означает, что ваш QuerySet файл, даже если он отфильтрован, слишком велик для хранения в памяти. Вы можете работать с .iterator(…) методом [Django-doc] для загрузки пакетов элементов, которые затем могут быть обработаны:

 results = Result.objects.filter(date__range=(date_from, date_to)).iterator()

for result in results:
    # …
    pass 

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

Комментарии:

1. Я бы упомянул невысказанный факт, который .filter() также дает набор запросов и так же ленив, как .all() и есть 🙂

2. @WillemVanOnsem Отлично! Я думаю, это то, что я хотел получить — наличие итератора, который будет выполнять выборку с помощью фрагментов. Будет реализован и сообщит вам, если это решит проблему. Спасибо за быстрый ответ!

3. @WillemVanOnsem, с которым я раньше не был знаком .iterator() . Итак, это означало, что это действительно то, что я искал 😊 Теперь нужно попробовать это в среде с этим большим набором данных

4. @WillemVanOnsem iterator() определенно улучшил ситуацию! Однако, когда проходит несколько более длительных периодов — его все равно убивают… Вероятно, нужно придумать что-то еще.

5. @EgorWexler: если это possbile, вы можете захотеть сделать меньшие «куски» диапазонов даты и времени и, таким образом, работать с разными запросами.