Медленный запрос в представлении UNION ALL

У меня есть представление БД, которое в основном состоит из двух запросов SELECT с UNION ALL, например:

CREATE VIEW v AS
SELECT time, etc. FROM t1 // #1...
UNION ALL
SELECT time, etc. FROM t2 // #2...

Проблема в том, что выбор формы

SELECT ... FROM v WHERE time >= ... AND time < ...

выполнять очень очень медленно на нем.

И SELECT #1, и #2 достаточно быстры, правильно индексированы и так далее: когда я создаю представления v1 и v2, например:

CREATE VIEW v1 AS
SELECT time, etc. FROM t1 // #1...

CREATE VIEW v2 AS
SELECT time, etc. FROM t2 // #2...

И тот же SELECT с тем же условием WHERE, что и выше, работает нормально на них по отдельности.

Любые идеи о том, где может быть проблема и как ее решить?

(Просто отметим, что это одна из последних версий Postgres.)

Изменить: добавление анонимных планов запросов (спасибо @filiprem за ссылку на отличный инструмент):

v1:

Aggregate  (cost=9825.510..9825.520 rows=1 width=53) (actual time=59.995..59.995 rows=1 loops=1)
  ->  Index Scan using delta on echo alpha  (cost=0.000..9815.880 rows=3850 width=53) (actual time=0.039..53.418 rows=33122 loops=1)
          Index Cond: (("juliet" >= 'seven'::uniform bravo_victor oscar whiskey) AND ("juliet" <= 'november'::uniform bravo_victor oscar whiskey))
          Filter: ((NOT victor) AND ((bravo_sierra five NULL) OR ((bravo_sierra)::golf <> 'india'::golf)))

v2:

Aggregate  (cost=15.470..15.480 rows=1 width=33) (actual time=0.231..0.231 rows=1 loops=1)
  ->  Index Scan using yankee on six charlie  (cost=0.000..15.220 rows=99 width=33) (actual time=0.035..0.186 rows=140 loops=1)
          Index Cond: (("juliet" >= 'seven'::uniform bravo oscar whiskey) AND ("juliet" <= 'november'::uniform bravo oscar whiskey))
          Filter: (NOT victor)

v:

Aggregate  (cost=47181.850..47181.860 rows=1 width=0) (actual time=37317.291..37317.291 rows=1 loops=1)
  ->  Append  (cost=42.170..47132.480 rows=3949 width=97) (actual time=1.277..37304.453 rows=33262 loops=1)
        ->  Nested Loop Left Join  (cost=42.170..47052.250 rows=3850 width=99) (actual time=1.275..37288.465 rows=33122 loops=1)
              ->  Hash Left Join  (cost=42.170..9910.990 rows=3850 width=115) (actual time=1.123..117.797 rows=33122 loops=1)
                      Hash Cond: ((alpha_seven.two)::golf = (quebec_three.two)::golf)
                    ->  Index Scan using delta on echo alpha_seven  (cost=0.000..9815.880 rows=3850 width=132) (actual time=0.038..77.866 rows=33122 loops=1)
                            Index Cond: (("juliet" >= 'seven'::uniform bravo_victor oscar whiskey_two) AND ("juliet" <= 'november'::uniform bravo_victor oscar whiskey_two))
                            Filter: ((NOT victor) AND ((bravo_sierra five NULL) OR ((bravo_sierra)::golf <> 'india'::golf)))
                    ->  Hash  (cost=30.410..30.410 rows=941 width=49) (actual time=1.068..1.068 rows=941 loops=1)
                            Buckets: 1024  Batches: 1  Memory Usage: 75kB
                          ->  Seq Scan on alpha_india quebec_three  (cost=0.000..30.410 rows=941 width=49) (actual time=0.010..0.486 rows=941 loops=1)
              ->  Index Scan using mike on hotel quebec_sierra  (cost=0.000..9.630 rows=1 width=24) (actual time=1.112..1.119 rows=1 loops=33122)
                      Index Cond: ((alpha_seven.zulu)::golf = (quebec_sierra.zulu)::golf)
        ->  Subquery Scan on "*SELECT* 2"  (cost=34.080..41.730 rows=99 width=38) (actual time=1.081..1.951 rows=140 loops=1)
              ->  Merge Right Join  (cost=34.080..40.740 rows=99 width=38) (actual time=1.080..1.872 rows=140 loops=1)
                      Merge Cond: ((quebec_three.two)::golf = (charlie.two)::golf)
                    ->  Index Scan using whiskey_golf on alpha_india quebec_three  (cost=0.000..174.220 rows=941 width=49) (actual time=0.017..0.122 rows=105 loops=1)
                    ->  Sort  (cost=18.500..18.750 rows=99 width=55) (actual time=0.915..0.952 rows=140 loops=1)
                            Sort Key: charlie.two
                            Sort Method:  quicksort  Memory: 44kB
                          ->  Index Scan using yankee on six charlie  (cost=0.000..15.220 rows=99 width=55) (actual time=0.022..0.175 rows=140 loops=1)
                                  Index Cond: (("juliet" >= 'seven'::uniform bravo_victor oscar whiskey_two) AND ("juliet" <= 'november'::uniform bravo_victor oscar whiskey_two))
                                  Filter: (NOT victor)

juliet is time.


person Mladen Jablanović    schedule 27.01.2012    source источник
comment
время столбца в вашем представлении не индексируется. Вам придется вручную индексировать этот столбец в своем представлении. Взгляните на план выполнения   -  person stian.net    schedule 27.01.2012
comment
Будут ли запросы к этому представлению всегда ограничены по времени?   -  person    schedule 27.01.2012
comment
@stian.net: Не уверен, что вы предлагаете. Я не могу добавить индексы в столбцы представления, и обе базовые таблицы правильно проиндексированы по полям времени.   -  person Mladen Jablanović    schedule 27.01.2012
comment
@MarkBannister: Да. Я хотел бы избежать создания материализованного представления или как бы оно ни называлось, если это будет предложением. :)   -  person Mladen Jablanović    schedule 27.01.2012
comment
Вы можете сказать, что действительно медленно? какое время для запросов № 1, № 2 и № 3? Вы можете просто показать результаты EXPLAIN (ANALYZE,BUFFERS) для всех запросов.   -  person filiprem    schedule 27.01.2012
comment
@filiprem: v1 — 60 мс, v2 — 0,2 мс, v — 37317 мс. Я не уверен, разрешено ли мне раскрывать фактические имена таблиц и полей, я мог бы заменить их общими именами и вставить сюда позже сегодня.   -  person Mladen Jablanović    schedule 27.01.2012
comment
Младен, объясни форматтер вывода и анонимайзер -› explain.depesz.com   -  person filiprem    schedule 27.01.2012
comment
@MladenJablanović: я больше думал о добавлении подсказок в представление (подсказки обычно устарели в представлениях, но могут быть допустимы, когда к представлению можно получить доступ только по определенному пути), но потом я обнаружил, что PostgreSQL не поддерживает использовать подсказки - см. здесь: stackoverflow.com/questions/309786/ и здесь: wiki .postgresql.org/wiki/OptimizerHintsDiscussion для соответствующих обсуждений, включая альтернативы подсказкам.   -  person    schedule 27.01.2012
comment
На 7-й день без явного ответа. Не могли бы вы также опубликовать два фактических запроса, а не только сам план запроса...   -  person DRapp    schedule 02.02.2012
comment
Что произойдет, если ваше представление будет на UNION DISTINCT вместо UNION ALL? Он работает быстрее? Это дает неправильные результаты?   -  person Walter Mitty    schedule 05.02.2012


Ответы (8)


Это похоже на ошибку пилота. План запроса "v" выбирает как минимум из 5 разных таблиц.

Теперь, вы уверены, что вы подключены к правильной базе данных? Может есть какие-то крутые настройки search_path? Может быть, t1 и t2 на самом деле являются представлениями (возможно, в другой схеме)? Может быть, Вы как-то выбираете из неправильного представления?

Отредактировано после уточнения:

Вы используете совершенно новую функцию под названием «удаление соединения»: http://wiki.postgresql.org/wiki/What%27s_new_in_PostgreSQL_9.0#Join_Removal

http://rhaas.blogspot.com/2010/06/why-join-removal-is-cool.html

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

другое редактирование: похоже, вы используете агрегат (например, «выбрать количество (*) из v» по сравнению с «выбрать * из v»), который может получить совершенно разные планы перед лицом удаления объединения. Думаю, мы не продвинемся далеко, если вы не опубликуете фактические запросы, представления и определения таблиц, а также используемые планы...

person maniek    schedule 30.01.2012
comment
v действительно запрашивает из ›2 разных таблиц, так как v1 и v2 тоже запрашивает из ›2 разных таблиц (для оценки разных столбцов). Просто кажется, что эти столбцы не оцениваются при извлечении из v1 и v2 по отдельности, а при запросе v. - person Mladen Jablanović; 30.01.2012
comment
Спасибо, это, кажется, ведет в правильном направлении. Я постараюсь предоставить больше информации о том, как выглядели эти запросы. - person Mladen Jablanović; 31.01.2012
comment
Возможно, вам повезет, посмотрите на этот коммит четырехдневной давности: git.postgresql.org/gitweb/ - выглядит актуально. Если вы используете версию 9.1, подождите, пока выйдет версия 9.1.3, и обновите ее. - person maniek; 03.02.2012
comment
@maniek: Я действительно сомневаюсь в его удаче. Ошибка № 6416 связана с индексами выражений, о которой не упоминалось. об этом в вопросе. - person Erwin Brandstetter; 03.02.2012
comment
@maniek: Вы были правы, в случаях планов выполнения, вставленных выше, я использовал COUNT(*). У меня не будет времени извлекать фактические запросы, которые мы используем, из самого приложения в ближайшие пару дней, но в конце концов я это сделаю. Являются ли удаления соединения причиной того, что не все таблицы из JOIN используются при запросе v1 и v2, но используются при запросе v? - person Mladen Jablanović; 03.02.2012
comment
@MladenJablanović: Удаление присоединения — это функция, которая в некоторых ограниченных случаях может определить, что присоединение к таблице не обязательно. Например, рассмотрим запрос: выберите t1.id из t1, соединив t2 с t2.t1_id=t1.id . Когда на t2.t1_id есть уникальный индекс, соединение с t2 избыточно, и планировщик может это понять. Но случаи, когда планировщик может понять это, ограничены, и я полагаю, что планировщик не может доказать, что соединения избыточны в более сложном представлении v. Это случай, который можно улучшить в более поздних версиях postgres. - person maniek; 04.02.2012

Я считаю, что ваш запрос выполняется аналогично:

(
   ( SELECT time, etc. FROM t1 // #1... )
   UNION ALL
   ( SELECT time, etc. FROM t2 // #2... )
)
WHERE time >= ... AND time < ...

которые оптимизатор испытывает трудности с оптимизацией. т. е. сначала выполняется UNION ALL перед применением предложения WHERE, но вы хотите, чтобы оно применяло предложение WHERE перед UNION ALL.

Не могли бы вы поместить пункт WHERE в пункт CREATE VIEW?

CREATE VIEW v AS
( SELECT time, etc. FROM t1  WHERE time >= ... AND time < ... )
UNION ALL
( SELECT time, etc. FROM t2  WHERE time >= ... AND time < ... )

В качестве альтернативы, если представление не может иметь предложение WHERE, то, возможно, вы можете придерживаться двух представлений и выполнять UNION ALL с предложением WHERE, когда они вам понадобятся:

CREATE VIEW v1 AS
SELECT time, etc. FROM t1 // #1...

CREATE VIEW v2 AS
SELECT time, etc. FROM t2 // #2...

( SELECT * FROM v1 WHERE time >= ... AND time < ... )
UNION ALL
( SELECT * FROM v2 WHERE time >= ... AND time < ... )
person Stephen Quan    schedule 03.02.2012

Я не знаю Postgres, но некоторые RMDB обрабатывают операторы сравнения хуже, чем BETWEEN в случае индексов. Я бы попытался использовать BETWEEN.

SELECT ... FROM v WHERE time BETWEEN ... AND ...
person Dimitri    schedule 05.02.2012

Можно было бы динамически выдавать новый SQL при каждом вызове вместо создания представления и интегрировать предложение where в каждый SELECT запроса на объединение.

SELECT time, etc. FROM t1
    WHERE time >= ... AND time < ...
UNION ALL
SELECT time, etc. FROM t2
    WHERE time >= ... AND time < ...

РЕДАКТИРОВАТЬ:

Можно ли использовать параметризованную функцию?

CREATE OR REPLACE FUNCTION CallMyView(t1 date, t2 date)
RETURNS TABLE(d date, etc.)
AS $$
    BEGIN
        RETURN QUERY
            SELECT time, etc. FROM t1
                WHERE time >= t1 AND time < t2
            UNION ALL
            SELECT time, etc. FROM t2
                WHERE time >= t1 AND time < t2;
    END;
$$ LANGUAGE plpgsql;

Вызов

SELECT * FROM CallMyView(..., ...);
person Olivier Jacot-Descombes    schedule 30.01.2012
comment
Это не работает для меня, мне определенно нужно одно представление, чтобы управлять ими всеми. :) - person Mladen Jablanović; 30.01.2012

Объедините две таблицы. Добавьте столбец для обозначения исходной таблицы. При необходимости замените исходные имена таблиц представлениями, которые выбирают только соответствующую часть. Проблема решена!

Изучение шаблона проектирования базы данных суперкласса/подкласса может быть вам полезно.

person ErikE    schedule 06.02.2012
comment
Не верил, что поможет, пока не попробовал. Теперь, когда условие WHERE касается одного и того же столбца из той же таблицы, запрос выполняется намного быстрее. Спасибо за этот совет! - person Ondřej Bouda; 11.08.2014

Попробуйте создать свое представление, используя UNION DISTINCT вместо UNION ALL. Посмотрите, дает ли это неправильные результаты. Посмотрите, дает ли это более высокую производительность.

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

Я очень подозрительно отношусь к ЛЕВЫМ СОЕДИНЕНИЯМ в показанном вами плане запроса. Нет необходимости выполнять LEFT JOINS, чтобы получить результаты, которые вы выбираете.

person Walter Mitty    schedule 05.02.2012

Столкнулся с таким же сценарием на 11g:

Сценарий 1:

CREATE VIEW v AS
  SELECT time, etc. FROM t1 // #1...

Следующий запрос выполняется быстро, план выглядит нормально:

SELECT ... FROM v WHERE time >= ... AND time < ...

Сценарий 2:

CREATE VIEW v AS
  SELECT time, etc. FROM t2 // #2...

Следующий запрос выполняется быстро, план выглядит нормально:

SELECT ... FROM v WHERE time >= ... AND time < ...

Сценарий 3 с UNION ALL:

CREATE VIEW v AS
  SELECT time, etc. FROM t1 // #1...
  UNION ALL
  SELECT time, etc. FROM t2 // #2...

Следующее работает медленно. Plan разбивает t1 и t2 (которые также были представлениями) и собирает их в виде большой серии союзов. Временные фильтры правильно применяются к отдельным компонентам, но все еще очень медленно:

SELECT ... FROM v WHERE time >= ... AND time < ...

Я был бы рад просто получить время примерное t1 плюс t2, но это было более чем вдвое. В этом случае мне помогло добавление подсказки parallel. Он упорядочил все по лучшему плану:

SELECT /*+ parallel */ ... FROM v WHERE time >= ... AND time < ...
person Glenn    schedule 06.05.2015

Думаю, у меня не так много баллов, чтобы опубликовать это в качестве комментариев, поэтому я публикую это как ответ

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

Ваше представление UNION ALL работает медленнее, потому что записи из SELECT #1 и #2 сначала объединяются во временную таблицу, который создается на лету, а затем ваш SELECT ... FROM v WHERE time >= ... AND time ‹ ... выполняется для этой временной таблицы. Поскольку и #1, и #2 индексируются, они работают быстрее по отдельности, как и ожидалось, но эта временная таблица не индексируется (конечно), и выбираются окончательные записи. из этой временной таблицы, что приводит к более медленному ответу.

Теперь, по крайней мере, я не вижу способа сделать это быстрее + ​​просмотр + нематериализованный

Одним из способов, кроме запуска SELECT #1 и #2 и их явного объединения, может быть использование хранимой процедуры или функции на языке программирования вашего приложения. (если это так), и в этой процедуре вы делаете отдельные вызовы для каждой индексированной таблицы, а затем объединяете результаты, что не так просто, как SELECT ... FROM v WHERE time >= ... AND time ‹ ... :(

person bjan    schedule 30.01.2012
comment
Я сомневаюсь, что здесь это так, поскольку из плана выполнения запроса v видно, что оба подзапроса ограничены полем time (julia), поэтому я почти уверен, что нет огромной временной таблицы создается, к которому впоследствии применяется ограничение time. - person Mladen Jablanović; 30.01.2012
comment
Я почти уверен, что нет огромной временной таблицы. Возможно, вы правы, но это может подтвердить администратор баз данных или тот, кто имеет точное представление о том, что происходит за кулисами. Пусть придет ответ, ответ, который точно объяснит, почему запрос V требует времени. - person bjan; 31.01.2012
comment
Oracle ›= 8i с оптимизатором на основе затрат (по умолчанию) обычно НЕ делает то, что вы говорите. Он сделает это, если оптимизатор решит, что это лучший/единственный вариант, но это случается не очень часто. - person gpeche; 03.02.2012
comment
@gpeche Я четко упомянул, что не могу оставлять комментарии, поэтому разместил его как ответ, что означает, что это не был точный ответ. Я использовал будет в ` Oracle будет работать` означает не ВСЕГДА, и вы тоже упомянули то же самое does NOT **usually**. Я опубликовал свой ответ (комментарий) 30 января с задержкой на 3 дня для вопроса с 200 наградами!!! вот почему я попытался дать @Mladen подсказку (я думаю, что никто не даст награду в 200, если (она) не находится в критической ситуации), но теперь, похоже, мне не следовало этого делать. Я должен опубликовать ответ только, если я уверен на 100%. Подождем точного ответа - person bjan; 03.02.2012
comment
@MladenJablanović: Часть 1: план объяснения v по сравнению с v1 и v2 включает дополнительные расходы на сортировку, слияние справа, сканирование подзапросов, Хеш, Хэш левое соединение, Вложенный цикл левого соединения, Добавление, так что очевидно, что этот запрос медленнее, потому что этих дополнительных расходов. Теперь вопрос, почему генерируется такой план выполнения, который как минимум не требуется для данного запроса? Может быть, это эвристика оптимизатора, или правило 80:20, или что-то еще... - person bjan; 07.02.2012
comment
@MladenJablanović: часть 2: это неясно в документах 9.1. Даже я не смог найти его при первом взгляде на Разработка и реализация оптимизатора запросов POSTGRES и Раскрыт оптимизатор PostgreSQL. Надеюсь, вы смогли найти какую-то подсказку из этих источников и сами получить точный ответ на свой вопрос :) - person bjan; 07.02.2012