Как оптимизировать запросы postgres

Я запускаю следующий запрос:

SELECT fat.*   
FROM Table1 fat  
LEFT JOIN modo_captura mc       ON mc.id = fat.modo_captura_id  
INNER JOIN loja lj              ON lj.id = fat.loja_id  
INNER JOIN rede rd              ON rd.id = fat.rede_id  
INNER JOIN bandeira bd          ON bd.id = fat.bandeira_id  
INNER JOIN produto pd           ON pd.id = fat.produto_id  
INNER JOIN loja_extensao le     ON le.id = fat.loja_extensao_id  
INNER JOIN conta ct             ON ct.id = fat.conta_id
INNER JOIN banco bc             ON bc.id = ct.banco_id  
LEFT JOIN conciliacao_vendas cv ON fat.empresa_id = cv.empresa_id AND cv.chavefato = fat.chavefato AND fat.rede_id = cv.rede_id  
WHERE 1 = 1  
AND cv.controle_upload_arquivo_id = 6906  
AND fat.parcela = 1  
ORDER BY fat.data_venda, fat.data_credito limit 20

Но очень медленно. Вот план объяснения: http://explain.depesz.com/s/DnXH


person Tiago Rolim    schedule 30.07.2014    source источник
comment
Ваша версия Postgres? Основная информация о цели запроса и ваших индексах? Какие из условий WHERE могут измениться и как?   -  person Erwin Brandstetter    schedule 30.07.2014
comment
В зависимости от того, как postgres обрабатывает критерии соединения и левое соединение, вы можете повысить производительность, переместив fat.parcela в начальное левое соединение. Почему? потому что фильтр применяется до того, как будет сгенерировано декартово значение, а не после. поэтому, если fat.parcela = 1 принимает 1 000 000 записей к 1, вы ЗНАЧИТЕЛЬНО сокращаете накладные расходы. чем меньше строк задействовано, тем меньше строк нужно материализовать/объединить, прежде чем будет выполнено предложение where. так что... ON mc.id = fat.modo_captura_id AND fat.parcela = 1 может быть намного быстрее... Это действительно зависит от того, КАК движок генерирует результаты.   -  person xQbert    schedule 30.07.2014
comment
@xQbert: как оказалось, вчера мы обсуждали это по этому связанному с этим вопросу: postgresql/25002962#25002962" title="пример запроса для отображения ошибки оценки количества элементов в postgresql">stackoverflow.com/questions/25002263/. По сути, Postgres свободно упорядочивает таблицы, если они соединены INNER JOIN, CROSS JOIN, or unadorned JOIN. LEFT JOIN ограничивает планировщика в его выборе.   -  person Erwin Brandstetter    schedule 30.07.2014
comment
Интересная ссылка. Мне все еще любопытно, если, переместив ограничивающие критерии в соединение, если движок уменьшит размер генерируемого декартова числа и, таким образом (с таким количеством соединений), вероятно, приведет к увеличению производительности. Я займусь этим позже, когда у меня будет больше свободного времени. Я не уверен, какие ограничения имеет левое соединение в postgresql (всегда ли система обрабатывает последние при создании набора результатов? приведет ли перемещение ограничения к объединению к повышению производительности, или движок достаточно умен, чтобы сделать это? Я' Я видел, как это происходит в обоих направлениях в других СУБД, не уверен, что произойдет здесь.   -  person xQbert    schedule 30.07.2014


Ответы (1)


Попробуйте эту переписанную версию:

SELECT fat.*   
FROM   Table1 fat
JOIN   conciliacao_vendas cv USING (empresa_id, chavefato, rede_id)
JOIN   loja lj               ON lj.id = fat.loja_id  
JOIN   rede rd               ON rd.id = fat.rede_id  
JOIN   bandeira bd           ON bd.id = fat.bandeira_id  
JOIN   produto pd            ON pd.id = fat.produto_id  
JOIN   loja_extensao le      ON le.id = fat.loja_extensao_id  
JOIN   conta ct              ON ct.id = fat.conta_id
JOIN   banco bc              ON bc.id = ct.banco_id
LEFT   JOIN modo_captura mc  ON mc.id = fat.modo_captura_id  
WHERE  cv.controle_upload_arquivo_id = 6906  
AND    fat.parcela = 1  
ORDER  BY fat.data_venda, fat.data_credito
LIMIT  20;

Синтаксис JOIN и последовательность соединений

В частности, я исправил вводящий в заблуждение LEFT JOIN на conciliacao_vendas, который в любом случае вынужден действовать как обычный [INNER] JOIN из-за более позднего условия WHERE. Это должно упростить планирование запросов и позволить исключить строки на ранних этапах процесса, что должно сделать все намного дешевле. Связанный ответ с подробным объяснением:

USING - это просто синтаксическое сокращение.

Поскольку в запросе задействовано много таблиц, а порядок, в котором переписанный запрос объединяет таблицы, теперь оптимален, вы можете точно настроить это с помощью SET LOCAL join_collapse_limit = 1, чтобы сократить накладные расходы на планирование и избежать некачественных планов запросов. Запустите в одной транзакции:

BEGIN;
SET LOCAL join_collapse_limit = 1;
SELECT ...;  -- read data here
COMMIT;      -- or ROOLBACK;

Подробнее об этом:

Показатель

Добавьте несколько индексов в справочные таблицы с лотами или строками (не обязательно всего пару десятков), в частности (взятые из вашего плана запроса):

Seq Scan на public.conta ct ... rows=6771
Seq Scan на public.loja lj ... rows=1568
Seq Scan на public .loja_extensao le ... rows=16394

Это особенно странно, потому что эти столбцы выглядят как столбцы первичного ключа и уже должны иметь индекс...

So:

CREATE INDEX conta_pkey_idx ON public.conta (id);
CREATE INDEX loja_pkey_idx ON public.loja (id);
CREATE INDEX loja_extensao_pkey_idx ON public.loja_extensao (id);

Чтобы сделать это действительно жирным, многоколоночный индекс был бы очень кстати:

CREATE INDEX foo ON Table1 (parcela, data_venda, data_credito);
person Erwin Brandstetter    schedule 30.07.2014
comment
Откуда вы знаете, что левое соединение вводило в заблуждение? Возможно, ограничение должно было быть наложено на соединение, так как OP могут потребоваться все записи из таблицы 1 и только те, которые совпадают в conciliacao_vendas.... - person xQbert; 30.07.2014
comment
@xQbert: Мой запрос возвращает тот же результат, что и исходный, но, возможно, быстрее. Подробное обоснование можно найти в связанном ответе. - person Erwin Brandstetter; 30.07.2014
comment
Я не спорю с вашим ответом. Я просто не понимаю, как вы пришли к выводу, что левое соединение было тем, что вводило в заблуждение, когда так же легко могло быть, что предложение where неверно, и текущие результаты также неверны. Если кто-то знает, как использовать LEFT JOIN, возможно, он просто не знает, как обращаться с ограничивающими критериями, когда используется LEFT JOIN... Но я отвлекся, это проблема OP... Мне очень нравится ваше обновление (без сарказма), Чтобы сделать это действительно FAT, многоколоночный индекс был бы очень кстати): - person xQbert; 30.07.2014
comment
@xQbert: О, я имел в виду быстро, но жир служит хорошо. :) - person Erwin Brandstetter; 30.07.2014