Применяются ли предложения WHERE в том порядке, в котором они написаны?

36

Я пытаюсь оптимизировать запрос, который просматривает большую таблицу (37 миллионов строк) и задает вопрос о том, в каком порядке выполняются операции в запросе.

select 1 
from workdays day
where day.date_day >= '2014-10-01' 
    and day.date_day <= '2015-09-30' 
    and day.offer_id in (
        select offer.offer_day 
        from offer  
        inner join province on offer.id_province = province.id_province  
        inner join center cr on cr.id_cr = province.id_cr 
        where upper(offer.code_status) <> 'A' 
            and province.id_region in ('10' ,'15' ,'21' ,'26' ,'31' , ...,'557') 
            and province.id_cr in ('9' ,'14' ,'20' ,'25' ,'30' ,'35' ,'37')
    )

Являются ли WHEREположения за указанный диапазон дат выполняются перед подзапросом? Это хороший способ поставить наиболее ограничивающие предложения в первую очередь, чтобы избежать больших циклов для других предложений, чтобы ускорить выполнение?

Теперь запросы занимают так много времени для выполнения.

Хорхе Вега Санчес
источник

Ответы:

68

Чтобы уточнить ответ @ alci:

PostgreSQL не волнует, в каком порядке вы пишете

  • PostgreSQL совершенно не заботится о порядке записей в WHEREпредложении и выбирает индексы и порядок выполнения, основываясь только на оценке стоимости и селективности.

  • Порядок, в котором записываются соединения, также игнорируется до настроенного join_collapse_limit; если есть больше соединений, чем это, он выполнит их в порядке их написания.

  • Подзапросы могут выполняться до или после запроса, который их содержит, в зависимости от того, что является самым быстрым, при условии, что подзапрос выполняется до того, как внешний запрос действительно нуждается в информации. Часто в действительности подзапрос выполняется как бы посередине или чередуется с внешним запросом.

  • Нет никаких гарантий, что PostgreSQL вообще выполнит части запроса. Они могут быть полностью оптимизированы. Это важно, если вы вызываете функции с побочными эффектами.

PostgreSQL преобразит ваш запрос

PostgreSQL будет сильно преобразовывать запросы, сохраняя те же самые эффекты, чтобы они выполнялись быстрее, не меняя результаты.

  • Термины вне подзапроса могут быть помещены в подзапрос, поэтому они выполняются как часть подзапроса, а не там, где вы написали их во внешнем запросе.

  • Термины в подзапросе могут быть перенесены во внешний запрос, поэтому их выполнение выполняется как часть внешнего запроса, а не там, где вы написали их в подзапросе.

  • Подзапрос может и часто сводится в соединение на внешней таблице. То же самое можно сказать и о таких вещах , как EXISTSи NOT EXISTSзапросы.

  • Представления сведены в запрос, который использует представление

  • Функции SQL часто вставляются в вызывающий запрос

  • ... и есть множество других преобразований, сделанных в запросах, таких как предварительная оценка константных выражений, декорреляция некоторых подзапросов и всевозможные другие приемы планирования / оптимизации.

В общем, PostgreSQL может массово преобразовать и переписать ваш запрос до точки, где каждый из этих запросов:

select my_table.*
from my_table
left join other_table on (my_table.id = other_table.my_table_id)
where other_table.id is null;

select *
from my_table
where not exists (
  select 1
  from other_table
  where other_table.my_table_id = my_table.id
);

select *
from my_table
where my_table.id not in (
  select my_table_id
  from other_table
  where my_table_id is not null
);

обычно все выдают точно такой же план запроса. (Предполагая, что в любом случае я не допустил никаких глупых ошибок).

Нет ничего необычного в том, чтобы попытаться оптимизировать запрос только для того, чтобы выяснить, что планировщик запросов уже определил уловки, которые вы пытаетесь, и применил их автоматически, поэтому оптимизированная вручную версия не лучше оригинальной.

Ограничения

Планировщик / оптимизатор далеко не универсален и ограничен требованием быть абсолютно уверенным в том, что он не может изменить эффекты запроса, доступные данные для принятия решений, реализованные правила и время ЦП. это может позволить себе потратить на обдумывание оптимизаций. Например:

  • Планировщик полагается на сохраняемую статистику ANALYZE(обычно через автовакуум). Если они устарели, выбор плана может быть плохим.

  • Статистические данные являются только выборкой, поэтому они могут вводить в заблуждение из-за эффектов выборки, особенно если выборка слишком мала. Неудачный выбор плана может привести к.

  • Статистика не отслеживает некоторые виды данных о таблице, такие как корреляции между столбцами. Это может привести к тому, что планировщик будет принимать неправильные решения, когда он предполагает, что вещи независимы, а они - нет.

  • Планировщик полагается на параметры стоимости, такие как random_page_costуказание относительной скорости различных операций в конкретной системе, на которой он установлен. Это только руководства. Если они сильно ошибаются, они могут привести к неправильному выбору плана.

  • Любой подзапрос с LIMITили OFFSETне может быть сплющен или подвергаться подтягиванию / опусканию. Это не означает, что он будет выполняться раньше всех частей внешнего запроса, или даже что он будет выполнен вообще .

  • Термины CTE (предложения в WITHзапросе) всегда выполняются полностью, если они выполняются вообще. Они не могут быть сплющены, и термины не могут быть сдвинуты вверх или вниз через терминологический барьер CTE. Термины CTE всегда выполняются перед окончательным запросом. Это нестандартное поведение, но задокументировано, как PostgreSQL работает.

  • PostgreSQL имеет ограниченную способность оптимизировать запросы к внешним таблицам, security_barrierпредставлениям и некоторым другим специальным типам отношений.

  • PostgreSQL не встроит функцию, написанную на чем-либо, кроме простого SQL, и не будет выполнять pullup / pushdown между ней и внешним запросом.

  • Планировщик / оптимизатор действительно глуп в выборе индексов выражений и в тривиальных различиях типов данных между индексом и выражением.

Тонны больше тоже.

Ваш запрос

В случае вашего запроса:

select 1 
from workdays day
where day.date_day >= '2014-10-01' 
    and day.date_day <= '2015-09-30' 
    and day.offer_id in (
        select offer.offer_day 
        from offer  
        inner join province on offer.id_province = province.id_province  
        inner join center cr on cr.id_cr = province.id_cr 
        where upper(offer.code_status) <> 'A' 
            and province.id_region in ('10' ,'15' ,'21' ,'26' ,'31' , ...,'557') 
            and province.id_cr in ('9' ,'14' ,'20' ,'25' ,'30' ,'35' ,'37')
    )

ничто не мешает превратить его в более простой запрос с дополнительным набором объединений, и, скорее всего, так и будет.

Вероятно, получится что-то вроде (не проверено, очевидно):

select 1 
from workdays day
inner join offer on day.offer_id = offer.offer_day
inner join province on offer.id_province = province.id_province  
inner join center cr on cr.id_cr = province.id_cr 
where upper(offer.code_status) <> 'A' 
   and province.id_region in ('10' ,'15' ,'21' ,'26' ,'31' , ...,'557') 
   and province.id_cr in ('9' ,'14' ,'20' ,'25' ,'30' ,'35' ,'37')
   and day.date_day >= '2014-10-01' 
   and day.date_day <= '2015-09-30';

Затем PostgreSQL оптимизирует порядок соединения и методы соединения, основываясь на его селективности и оценках количества строк и доступных индексах. Если они разумно отражают реальность, тогда они выполнят объединения и выполнят записи в предложении where в любом порядке - часто смешивая их вместе, поэтому немного, а потом немного возвращается к первой части. , так далее.

Как посмотреть, что сделал оптимизатор

Вы не можете видеть SQL, в который PostgreSQL оптимизирует ваш запрос, потому что он преобразует SQL во внутреннее представление дерева запросов, а затем изменяет его. Вы можете сбросить план запроса и сравнить его с другими запросами.

Нет никакого способа «отменить» этот план запроса или внутреннее дерево плана до SQL.

У http://explain.depesz.com/ есть достойный помощник по плану запросов. Если вы совершенно не знакомы с планами запросов и т. Д. (В этом случае я удивлен, что вы сделали это так далеко через этот пост), тогда PgAdmin имеет графическое средство просмотра планов запросов, которое предоставляет гораздо меньше информации, но проще.

Связанное чтение:

Pushdown / подтягивающие и уплощение возможности продолжать улучшаться в каждом выпуске . PostgreSQL обычно прав насчет решений подтягивания / опускания / сплющивания, но не всегда, поэтому иногда приходится (ab) использовать CTE или OFFSET 0взламывать. Если вы обнаружите такой случай, сообщите об ошибке планировщика запросов.


Если вы действительно, очень заинтересованы, вы также можете использовать debug_print_plansопцию, чтобы увидеть план необработанных запросов, но я обещаю, что вы не хотите читать это. В самом деле.

Крейг Рингер
источник
Ничего себе ... довольно полный ответ :-) Один из случаев, когда у меня были медленные планы с Postgresql (а также с другими хорошо известными механизмами БД, такими как Oracle), связан с корреляциями между столбцами или несколькими коррелированными объединениями. Он часто будет выполнять вложенные циклы, думая, что в этой точке плана всего несколько строк, а на самом деле их много. Один из способов оптимизации запросов такого типа - установить enable_nestloop = off; на время запроса.
Алси
Я столкнулся с ситуацией, когда v9.5.5 пытался применить TO_DATE перед проверкой, можно ли его применить, в простом запросе 7 where. Заказ имеет значение.
user1133275
@ user1133275 В этом случае это сработало только для вас случайно, потому что расчетные оценки затрат были одинаковыми. PostgreSQL может по-прежнему решить запустить to_dateперед проверкой в ​​более поздней версии или из-за изменения статистики оптимизатора. Чтобы надежно выполнить проверку перед функцией, которая должна запускаться только после проверки, используйте CASEоператор.
Крейг Рингер
один из величайших ответов, которые я когда- либо видел на SO! Недурно, мужик!
62мкв
Я сталкивался с ситуациями, когда простое добавление запроса order byвыполняло запрос намного быстрее, чем если бы его не было order by. Это одна из причин, по которой я пишу свои запросы с помощью объединений таким образом, как если бы я хотел, чтобы они выполнялись - приятно иметь отличный оптимизатор, но я считаю, что не стоит полностью доверять своей судьбе ее результатам и писать запросы, не задумываясь о том, как это may beвыполнено ... Отличный ответ !!
Грегори
17

SQL - это декларативный язык: вы говорите, что хотите, а не как это делать. СУБД выберет способ выполнения запроса, который называется планом выполнения.

Когда-то (5-10 лет назад) способ написания запроса оказывал непосредственное влияние на план выполнения, но в настоящее время большинство механизмов баз данных SQL используют оптимизатор на основе затрат для планирования. То есть он будет оценивать различные стратегии выполнения запроса на основе своей статистики по объектам базы данных и выбирать лучшую.

В большинстве случаев это действительно лучший вариант, но иногда механизм БД делает неправильный выбор, что приводит к очень медленным запросам.

ALCI
источник
Следует отметить, что в некоторых СУБД порядок запросов по-прежнему важен, но для более продвинутых все, что вы говорите, верно как на практике, так и в теории. Когда планировщик запросов выбирает неправильный выбор порядка выполнения, обычно имеются подсказки запроса, чтобы подтолкнуть его в более эффективном направлении (например, WITH(INDEX(<index>))в MSSQL для принудительного выбора индекса для конкретного соединения).
Дэвид Спиллетт
Вопрос в том, существует ли какой-то индекс на date_dayсамом деле. Если его нет, оптимизатор не планирует много планов для сравнения.
jkavalik