Как сделать производный 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-.

Вторая часть вопроса - вопрос ли это? - Прежде всего, у вас должен быть работающий, ощутимый - настолько ощутимый, насколько это возможно - продукт. Сделайте его публичным - внутренний круг - и сообщество - включая вас - сделает все остальное. Я бы не спешил с оптимизацией. Оптимизация, конечно, должна быть в наших мыслях. Однако, когда мы осознаем эту мысль в своем сознании, мы должны наблюдать, изменяет ли она наш поток, создает ли стресс - умственную нагрузку - или нет. Если да, то, возможно, она оставляет нас - наш поток - позади.

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