Sergei_Erjemin
@Sergei_Erjemin
Улыбайся, будь самураем...

Почему в Django преобразование List(QuerySet) такое медленное?

Люблю писать raw запросы в Django. И не то чтобы не умею готовить ORM (хотя готовлю не важно), а просто даже на SQL иногда не получается написать то что хочешь (см. мой предыдущий вопрос). И вот накатав очередной raw с кучей всяких JOIN из шести таблиц (назовет результат его исполнения QueySet2), и посмотрев в профилировщике сколько он кушает, решил всё безумное число повторяющихся полей таскать из предыдущего raw запроса (результат его назовем QuerySet1). Все равно он в кеше сидит, поля совпадают, id не повторяются... в моем случае это мне показалось удобным. Все равно надо весть этот QueySet2 прелопатить для отправки в шаблон выкинув 90% повторяющихся данных...

Чтобы было понятнее, происходит что-то вроде формирования таблицы... "листаю" записи QueySet2, раскладываю их в колонки и ряды, добавляю к каждому колонке и ряду какие-то справочные данные полученные из вычисляемых полей и полей связных таблиц навороченного QueySet2... А вот сами описания этих колонок, рядов и каждой ячейки можно взять из QyerySet1. И вот я обращаюсь к QyerySet1 примерно таким образом: QyerySet1[i].id ... Таких обращений всего 35 в цикле (посчитал). И выполеятся это 4.71 секунды. Но т.к. для определения длинны QyerySet1 все равно надо было -- для листания ячеек по рядам -- определить его длину len(list(QyerySet1)), то ради эксперимента решил проверить как это будет работать если сначала сделать ListSet1 = list(QyerySet1) а уже после обращаться ListSet1[i].id.... И те же 35 запросов выполнились за 0.25 секунды! Т.е. быстрее в 18 раз!

Время замерено без учета исполнения самого SQL (время исполнения SQL посмотрел в SQL-профилировщике и вычел из обоих замеров... Если не вычитать разница будет 12 раз, что тоже прилично).

Поэспериментировав, убедился, что обращение QyerySet1[i].id похоже каждый раз внутри Django превращается в List(QyerySet1)[i].id (я не программист, по-этому, в исходники лазит не умею, и делаю выводы просто сравнивая время исполнения разных вариантов).

Вопрос почему так медленно? Как это победить?
  • Вопрос задан
  • 1064 просмотра
Решения вопроса 2
@deliro
Научись работать с Django
Научись работать с Python (в частности, pep8 и iterable)
Научись пользоваться ORM (only, defer, select/prefetch related) и использовать raw как исключение, а не наоборот
Научись строить архитектуру БД (в частности,
кучей всяких JOIN из шести таблиц
)

И не будет таких медленных запросов.

А ответом на вопрос будет примерно вот что:
Если QuerySet1 ещё не сфетчен, то QyerySet1[i].id делает запрос к БД (итого, 35 запросов)
list(QyerySet1) делает фетч и .id уже берётся из оперативы.

P.S. Используй django-debug-toolbar. Увидишь все свои SQL запросы и время их исполнения.
Ответ написан
Sergei_Erjemin
@Sergei_Erjemin Автор вопроса
Улыбайся, будь самураем...
Возня с отладчиком показал:

Проблема в "Отложенной загрузке полей" при исполнении raw-запросов! В большом-пребольшом raw забыл указать одно из полей (не люблю использовать звездочки, т.к. хочется иметь названия всех полей перед глазами). Все работает, но при получении значения из неуказанного поля выполнялся еще один запрос! Соответственно, при выполнении list() никакой "Отложенной загрузкой полей" в этом большом-пребольшом запросе происходило, а происходил маленький запрос get по id в нужную таблицу (Django сам понимал, какие данные из БД хотят и сам строил маленький get-запрос). Понятно, что выполнение 35 маленьких get запросов быстрее чем 35 больших-пребольших.

Добавление нужного поля в большой-пребольшой запрос решило проблему. Аналогично ее решает использование звездочек в SQL-запросе.
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы