Как сделать производный select в Django, чтобы сузить круг до более дорогих операций?
Мы разрабатываем отчет по набору данных transaction
, собранному с динамическими атрибутами EAV с помощью операторов left outer join
. Сниппет SQL limit 20 OFFSET 27000
имитирует пагинацию по 20 записей на странице в середине набора данных, а общий размер составляет почти 30 тысяч transaction
строк.
В исходном коде Django, QuerySet начинается с модели Transaction
и фильтрует ее active_objects
перед добавлением операторов left outer join
. Смотрите следующий фрагмент для фильтрации QuerySet:
transaction = Transaction.active_objects.filter(
product_id=product_id
).order_by('-id').all()[27000:27020]
В качестве системного поведения Django по умолчанию, я полагаю, что ORM помещает предложение WHERE
в конец SQL-запроса. В результате база данных MariaDB v10.5 выполняет все операторы left outer join
над полным набором данных, прежде чем применить условие фильтрации. Время выполнения в нашем тесте составляет почти 20 секунд.
Мы вручную улучшили SQL-запрос и сократили время выполнения до менее одной секунды.
Изменения заключаются в перемещении пункта WHERE
вверх, сразу после пункта FROM app_transaction
и заключении его в скобки ( )
, чтобы явно сделать "производный select". Таким образом, мы заставили запрос сузиться до одной страницы данных, прежде чем собирать дорогостоящие части left outer join
. См. приведенный ниже фрагмент SQL для производного select:
...
from
(
select * from
`app_transaction`
where
(not `app_transaction`.`rec_del`
and `app_transaction`.`product_id` = __PRODUCT_ID__)
order by
`app_transaction`.`id` desc
limit 20 OFFSET 27000
) as app_transaction
...
Наш вопрос:
Как в рамках фреймворка Django реализовать доработку вручную, желательно не сырой запрос? Мы будем очень признательны за любые подсказки и предложения.
Кроме того, мы хотим сузить его до меньшего набора данных, прежде чем переходить к более дорогой части вычислений, поэтому мы открыты для других идей, которые могут служить той же цели.
Технические детали:
- Оригинальный SQL-запрос, сгенерированный Django ORM, занимающий почти 20 секунд на нашей тестовой установке (включая компьютер и размер набора данных):
select
`app_transaction`.`id`,
`app_transaction`.`product_id`,
-- ... a few more native columns of the `transaction` table
eav_firstname.`value_text` as `eav_firstname`,
-- ... 20+ more EAV attributes assembled by the left outer join operations
from
`app_transaction`
left outer join `eav_value` eav_firstname on
(`app_transaction`.`id` = eav_firstname.`entity_id`
and (eav_firstname.`entity_ct_id` = __CONTENTTYPE_ID__)
and (eav_firstname.`attribute_id` = __ATTRIBUTE_ID_OF_FIRSTNAME__))
-- ... 20+ more left outer join operations to assemble the EAV attributes
where
(not `app_transaction`.`rec_del`
and `app_transaction`.`product_id` = __PRODUCT_ID__)
order by
`app_transaction`.`id` desc
limit 20 OFFSET 27000
;
- Усовершенствованный вручную SQL-запрос, занимающий менее одной секунды времени выполнения при тех же настройках, что и выше:
select
`app_transaction`.`id`,
`app_transaction`.`product_id`,
-- ... a few more native columns of the `transaction` table
eav_firstname.`value_text` as `eav_firstname`,
-- ... 20+ more EAV attributes assembled by the left outer join operations
from
(
select * from
`app_transaction`
where
(not `app_transaction`.`rec_del`
and `app_transaction`.`product_id` = __PRODUCT_ID__)
order by
`app_transaction`.`id` desc
limit 20 OFFSET 27000
) as app_transaction
left outer join `eav_value` eav_firstname on
(`app_transaction`.`id` = eav_firstname.`entity_id`
and (eav_firstname.`entity_ct_id` = __CONTENTTYPE_ID__)
and (eav_firstname.`attribute_id` = __ATTRIBUTE_ID_OF_FIRSTNAME__))
-- ... 20+ more left outer join operations to assemble the EAV attributes
;
Я бы перепроверил документацию Django -ORM- и - или - попытался бы найти какое-то успокаивающее состояние в своем сознании. Многие ответы - те, которые кажутся сложными или трудными - можно решить, просто отпустив ситуацию или просто расслабившись. Необходимо создать некую зону комфорта. Попросите немного времени, чтобы разобраться в этом. Если хотите, сделайте наблюдение за тем, кто управляет вашим восприятием времени. Обычно кажется, что это делает ваш администратор. Но как воспринимаете вы?
ФОРМЫ - это абстракции более высокого уровня, своего рода консенсус для достижения лучших практик - они также относительны. Но в конечном итоге они зависят от необработанных запросов. Не писать необработанные запросы - это своего рода предварительное предпочтение -внешнее-, вместо того, чтобы нервничать по этому поводу, я бы попробовал, если сочту нужным (своего рода способ открытия) - Это, конечно, требует зоны комфорта-. Другим вариантом (как способ открытия) может быть: Попытка написать более высокий уровень абстракции на Django -ORM-.
Вторая часть вопроса - вопрос ли это? - Прежде всего, у вас должен быть работающий, ощутимый - настолько ощутимый, насколько это возможно - продукт. Сделайте его публичным - внутренний круг - и сообщество - включая вас - сделает все остальное. Я бы не спешил с оптимизацией. Оптимизация, конечно, должна быть в наших мыслях. Однако, когда мы осознаем эту мысль в своем сознании, мы должны наблюдать, изменяет ли она наш поток, создает ли стресс - умственную нагрузку - или нет. Если да, то, возможно, она оставляет нас - наш поток - позади.