У меня очень большая таблица. Он в настоящее время находится в базе данных MySQL. Я использую django.
Мне нужно перебрать элемент каждый таблицы, чтобы предварительно вычислить некоторые данные (возможно, если бы я был лучше, я мог бы сделать иначе, но это не так).
Я хотел бы сохранить итерацию как можно быстрее при постоянном использовании памяти.
Как уже ясно в Ограничение использования памяти в * Большом * Django QuerySet и Почему выполняется итерация через большой Django QuerySet, потребляющий огромное количество памяти?, простая итерация по всем объектам в django будет убивать машину, поскольку она будет извлекать ВСЕ объекты из базы данных.
На пути к решению
Прежде всего, чтобы уменьшить потребление памяти, вы должны быть уверены, что DEBUG является False (или обезьяна паттирует курсор: отключает ведение журнала SQL при сохранении настроек .DEBUG?), чтобы убедиться, что django не хранит файлы в connections
для отладки.
Но даже при этом
for model in Model.objects.all()
- это не гонка.
Даже при немного улучшенной форме:
for model in Model.objects.all().iterator()
Используя iterator()
, вы сохраните некоторую память, не сохраняя результат кэша внутренне (хотя и не обязательно на PostgreSQL!); но по-прежнему будут извлекать все объекты из базы данных.
Наивное решение
Решение в первом вопросе состоит в том, чтобы обрезать результаты на основе счетчика с помощью chunk_size
. Существует несколько способов написать его, но в основном все они сводятся к запросу OFFSET + LIMIT
в SQL.
что-то вроде:
qs = Model.objects.all()
counter = 0
count = qs.count()
while counter < count:
for model in qs[counter:counter+count].iterator()
yield model
counter += chunk_size
Хотя это эффективная память (постоянное использование памяти пропорционально chunk_size
), она очень плохая с точки зрения скорости: по мере того, как OFFSET растет, как MySQL, так и PostgreSQL (и, вероятно, большинство БД) начнут задыхаться и замедляться.
Лучшее решение
Лучшее решение доступно в этот пост от Thierry Schellenbach. Он фильтрует на ПК, который быстрее, чем компенсирует (насколько быстро, возможно, зависит от БД)
pk = 0
last_pk = qs.order_by('-pk')[0].pk
queryset = qs.order_by('pk')
while pk < last_pk:
for row in qs.filter(pk__gt=pk)[:chunksize]:
pk = row.pk
yield row
gc.collect()
Это начинает становиться удовлетворительным. Теперь Memory = O (C) и Speed ~ = O (N)
Проблемы с "лучшим" решением
Лучшее решение работает только тогда, когда PK доступен в QuerySet. К несчастью, это не всегда так, в частности, когда QuerySet содержит комбинации разных (group_by) и/или значений (ValueQuerySet).
В этой ситуации "лучшее решение" не может быть использовано.
Можем ли мы лучше?
Теперь мне интересно, можем ли мы пойти быстрее и избежать проблемы с QuerySets без ПК. Возможно, используя что-то, что я нашел в других ответах, но только в чистом SQL: используя курсоры.
Так как я плохо разбираюсь в необработанном SQL, в частности в Django, возникает реальный вопрос:
как мы можем построить лучший итератор Django QuerySet для больших таблиц
Мое взятие из того, что я прочитал, заключается в том, что мы должны использовать серверные курсоры (видимо (см. ссылки), используя стандартный Django Cursor, не достигли бы такого же результата, потому что по умолчанию оба соединения python-MySQL и psycopg кэшируют результаты).
Будет ли это действительно более быстрое (и/или более эффективное) решение?
Можно ли это сделать, используя raw SQL в django? Или мы должны писать конкретный код python в зависимости от соединителя базы данных?
Курсоры на стороне сервера в PostgreSQL и в MySQL
Что, насколько я мог получить на данный момент...
a Django chunked_iterator()
Теперь, лучше всего, этот метод будет работать как queryset.iterator()
, а не iterate(queryset)
, и будет частью ядра django или, по крайней мере, подключаемого приложения.
Обновление. Благодаря "Т" в комментариях для поиска django ticket, которые содержат некоторую дополнительную информацию. Различия в поведении коннектора делают его таким, чтобы, вероятно, лучшим решением было бы создать конкретный метод chunked
, а не прозрачно расширяться iterator
(звучит как хороший подход ко мне).
Реализация stub существует, но не было никакой работы в год, и не похоже, что автор готов перейти на что еще.
Дополнительные ссылки:
- Почему смещение MYSQL выше LIMIT замедляет запрос вниз?
- Как ускорить запрос MySQL с большим смещением в предложении LIMIT?
- http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/
- postgresql: смещение + ограничение становится очень медленным
- Улучшение производительности OFFSET в PostgreSQL
- http://www.depesz.com/2011/05/20/pagination-with-fixed-order/
- Как получить строку за строкой MySQL ResultSet в python Серверный курсор в MySQL
редактирует:
Django 1.6 добавляет постоянные соединения с базой данных
Постоянные соединения базы данных Django
Это должно облегчить, при некоторых условиях, использование курсоров. Тем не менее это за пределами моих текущих навыков (и времени, чтобы узнать), как реализовать такое решение.
Кроме того, "лучшее решение" определенно не работает во всех ситуациях и не может использоваться в качестве универсального подхода, а только заглушка должна быть адаптирована в каждом случае...