Медленный запрос в представлении "UNION ALL" - PullRequest
14 голосов
/ 27 января 2012

У меня есть представление БД, которое в основном состоит из двух 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 - это time.

Ответы [ 8 ]

9 голосов
/ 30 января 2012

Это похоже на ошибку пилота. План запроса "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»), который может получить совершенно разные планы перед удалением соединения. Я полагаю, что мы не сможем продвинуться слишком далеко без того, чтобы Вы опубликовали актуальные запросы, определения таблиц и использованные планы и планы ...

5 голосов
/ 04 февраля 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 < ... )
2 голосов
/ 05 февраля 2012

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

SELECT ... FROM v WHERE time BETWEEN ... AND ...
1 голос
/ 06 февраля 2012

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

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

1 голос
/ 30 января 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;

Call

SELECT * FROM CallMyView(..., ...);
0 голосов
/ 06 мая 2015

Обнаружен тот же сценарий 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...

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

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

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

SELECT /*+ parallel */ ... FROM v WHERE time >= ... AND time < ...
0 голосов
/ 05 февраля 2012

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

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

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

0 голосов
/ 30 января 2012

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...