Объясняя необъяснимое

http://www.depesz.com/2013/04/16/explaining-the-unexplainable/
  • Перевод
Друзья, мы с радостью продолжаем публикацию интересных материалов, посвященных самым разнообразным аспектам работы с PostgreSQL. Сегодняшний перевод открывает целую серию статей за авторством Hubert Lubaczewski, которые наверняка заинтересуют широкий круг читателей.



Одна из первых вещей, которую слышит новоиспеченный администратор баз данных – «используй EXPLAIN». И при первой же попытке он сталкивается c непостижимым:

                                                        QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
 Sort  (cost=146.63..148.65 rows=808 width=138) (actual time=55.009..55.012 rows=71 loops=1)
   Sort Key: n.nspname, p.proname, (pg_get_function_arguments(p.oid))
   Sort Method: quicksort  Memory: 43kB
   ->  Hash Join  (cost=1.14..107.61 rows=808 width=138) (actual time=42.495..54.854 rows=71 loops=1)
         Hash Cond: (p.pronamespace = n.oid)
         ->  Seq Scan on pg_proc p  (cost=0.00..89.30 rows=808 width=78) (actual time=0.052..53.465 rows=2402 loops=1)
               Filter: pg_function_is_visible(oid)
         ->  Hash  (cost=1.09..1.09 rows=4 width=68) (actual time=0.011..0.011 rows=4 loops=1)
               Buckets: 1024  Batches: 1  Memory Usage: 1kB
               ->  Seq Scan on pg_namespace n  (cost=0.00..1.09 rows=4 width=68) (actual time=0.005..0.007 rows=4 loops=1)
                     Filter: ((nspname <> 'pg_catalog'::name) AND (nspname <> 'information_schema'::name))

Что бы это могло значить?

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

PostgreSQL запоминает

Это значит, что PostgreSQL хранит некоторую мета-информацию (информацию об информации). Количество строк, количество различных значений, наиболее распространенные значения и так далее. Для больших таблиц эта информация основывается на случайной выборке, но в целом Постгрес действительно многое знает о наших данных.

Итак, давайте рассмотрим простой запрос и его explain:

$ explain select * from test where i = 1;
                      QUERY PLAN                      
------------------------------------------------------
 Seq Scan on test  (cost=0.00..40.00 rows=12 width=4)
   Filter: (i = 1)
(2 rows)

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

В explain первая строка и все строки, начинающиеся с “->" – это операции. Остальные строки – дополнительная информация к операции выше.

В нашем случае есть всего одна операция – последовательное сканирование таблицы test.

Также есть дополнительная информация о фильтре.

Последовательное сканирование означает, что PostgreSQL будет «открывать» данные из таблицы и читать их. Теоретически, он может отфильтровывать (удалять) строки, но, в целом, готов прочитать и вернуть всю таблицу целиком.

Почему готов? Объясню через минуту.

Итак, строка Seqscan информирует нас о том, что мы сканируем таблицу в последовательном режиме. И что таблица называется “test" (хотя вот тут кроется одна из самых больших проблем explain – он не показывает схему, и это мне аукалось не один раз).

А что же это за числа в скобках после операции?

Хочу задать вам вопрос. У вас есть вот такая таблица:

                            Table "public.t"
   Column    |  Type   |                   Modifiers                    
-------------+---------+------------------------------------------------
 id          | integer | not null default nextval('t_id_seq'::regclass)
 some_column | integer | 
 something   | text    | 
Indexes:
    "t_pkey" PRIMARY KEY, btree (id)
    "q" btree (some_column)

Имея определение этой таблицы и запрос:

SELECT * FROM t where some_column = 123;

Как вы думаете, каким способом лучше всего выполнить этот запрос? Последовательно просканировать таблицу или использовать индекс?

Если ваш ответ: конечно, использовать индекс, на этой колонке есть индекс, так что такой способ будет быстрее, — то я спрошу: как насчет ситуации, когда в таблице всего одна строка, и она содержит значение some_column, равное 123?

Чтобы произвести последовательное сканирование, мне нужно прочитать всего одну страницу таблицы (8192 байт), и я получу строку. Для того чтобы использовать индекс, мне нужно прочитать страницу из индекса, проверить, есть ли в таблице строки, соответствующие условию, а потом прочитать страницу из таблицы.

В итоге – вдвое больше работы!

Вы могли бы сказать: «Конечно, но речь ведь об очень маленьких таблицах, так что скорость не имеет значения». Хорошо. Тогда давайте представим таблицу, в которой 10 миллиардов строк, и у каждой из них some_column = 123. Индекс здесь точно не поможет, а в реальности вообще серьезно ухудшит ситуацию.

Конечно, если у вас миллион строк и только у одной из них some_column = 123, сканирование индексов будет наиболее правильным решением.

Таким образом, невозможно сказать, будет ли данный запрос использовать индекс, и надо ли ему вообще использовать индекс (речь идёт об общих случаях). Чтобы понять это, вам нужно больше информации. И этот факт приводит нас к простому выводу: в зависимости от ситуации один способ получения данных будет лучше или хуже другого.

PostgreSQL (до определенного момента) проверяет все возможные сценарии. Он знает, сколько у вас строк и сколько строк (вероятнее всего) попадут под заданные критерии, поэтому может принимать весьма умные решения.

Но как принимаются эти решения? Именно это и показывает первый набор цифр в explain. Это затраты.

Некоторые думают, что затраты оцениваются в секундах. Это не так. Их единица измерения – «извлечение одной страницы в последовательном (sequential) порядке». То есть оценивается и время, и использование ресурсов.

В postgresql.conf вы могли заметить вот такие параметры:

seq_page_cost = 1.0                    # measured on an arbitrary scale
random_page_cost = 4.0                 # same scale as above
cpu_tuple_cost = 0.01                  # same scale as above
cpu_index_tuple_cost = 0.005           # same scale as above
cpu_operator_cost = 0.0025             # same scale as above

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

Давайте для примера создадим простую таблицу из 1000 строк с какими-нибудь текстами и индексом:

create table test (id serial primary key, some_text text);
CREATE TABLE
 
insert into test (some_text) select 'whatever' from generate_series(1,1000);
INSERT 0 1000

Мы видим, что запуск explain с условием по id выдаёт следующее:

explain select * from test where id = 50;
                              QUERY PLAN                               
-----------------------------------------------------------------------
 Index Scan using test_pkey on test  (cost=0.28..8.29 rows=1 width=36)
   Index Cond: (id = 50)
(2 rows)

А что если бы мы сказали постгресу, что сканирование индекса нельзя использовать ни при каких условиях?

explain select * from test where id = 50;
                               QUERY PLAN                               
------------------------------------------------------------------------
 Bitmap Heap Scan on test  (cost=4.28..8.30 rows=1 width=13)
   Recheck Cond: (id = 50)
   ->  Bitmap Index Scan on test_pkey  (cost=0.00..4.28 rows=1 width=0)
         Index Cond: (id = 50)
(4 rows)

И это тоже давайте отключим:

explain select * from test where id = 50;
                      QUERY PLAN                      
------------------------------------------------------
 Seq Scan on test  (cost=0.00..18.50 rows=1 width=13)
   Filter: (id = 50)
(2 rows)

OK, а теперь давайте выведем их рядом друг с другом:

Index Scan using test_pkey on test  (cost=0.28..8.29 rows=1 width=36)
Bitmap Heap Scan on test  (cost=4.28..8.30 rows=1 width=13)
Seq Scan on test  (cost=0.00..18.50 rows=1 width=13)

По умолчанию постгрес использует IndexScan. Почему? Всё просто – в данном случае это наименее затратный способ. Издержки составят 8.29, в то время как для bitmap heap scan (что бы это ни было) потребуется 8.30, а для seq scan – 18.5.

OK, но издержки обозначаются двумя числами: number..number. Что это такое, и почему я говорю только о втором числе? Если бы мы учитывали первое число, то победителем оказался бы seq scan, так как у него это значение равно нулю, а у indexscan – 0.28, и аж 4.28 у bitmap heap scan.

Значение издержек выводится в диапазоне (number ..number), потому что оно показывает затраты на строку начала операции и затраты на получение всех строк (под всеми имеются в виду возвращенные этой операцией, а не все, имеющиеся в таблице).

Каковы начальные издержки? Для seqscan их нет – вы просто читаете страницу и возвращаете строки. И всё. Но, например, для сортировки набора данных вам нужно прочитать все данные и действительно отсортировать их прежде, чем рассматривать возможность возврата даже первой из строк. Это хорошо видно на следующем explain:

                            QUERY PLAN                             
-------------------------------------------------------------------
 Sort  (cost=22.88..23.61 rows=292 width=202)
   Sort Key: relfilenode
   ->  Seq Scan on pg_class  (cost=0.00..10.92 rows=292 width=202)
(3 rows)

Обратите внимание, что начальные затраты для Sort – 22.88, в то время как общие издержки составят всего 23.61. Так что возвращение строк из Sort с точки зрения затрат незначительно, а вот их сортировка – да.

Следующая информация в explain – это “rows". Это приблизительное количество строк, которое, как считает PostgreSQL, эта операция способна вернуть (она может вернуть меньше, например, в случае наличия LIMIT). Это также очень важно для некоторых операций — например, объединений (join). Объединение двух таблиц, в которых суммарно имеется 20 строк, может осуществляться множеством способов, и, по большому счету, неважно, каким именно. Но когда вы объединяете таблицу из миллиона строк с таблицей из миллиарда строк, способ, которым вы это делаете, очень важен (я говорю не об inner join / left join, а скорее о hash join, nested loop, merge join – если вы не понимаете, о чем речь, не переживайте, я всё объясню чуть позже).

Конечно, это число может быть оценено неверно – по многим причинам. Иногда это не имеет значения, а иногда имеет. Но о неправильных оценках мы поговорим позже.

Последний кусочек информации – ширина (width). Это оценка PostgreSQL того, сколько, в среднем, байт содержится в одной строке, возвращенной в рамках данной операции. Например:

explain select * from pg_class;
                         QUERY PLAN                          
-------------------------------------------------------------
 Seq Scan on pg_class  (cost=0.00..10.92 rows=292 width=202)
(1 row)
 
explain select relname, relkind from pg_class;
                         QUERY PLAN                         
------------------------------------------------------------
 Seq Scan on pg_class  (cost=0.00..10.92 rows=292 width=65)
(1 row)

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

А теперь внимание, наиболее важная информация: explain’ы – это деревья. Верхнему узлу требуются данные от узлов, расположенных под ним.

Давайте рассмотрим этот план.

В нём 5 операций: сортировка, hash join, seq scan, hash и снова seq scan. PostgreSQL выполняет верхнюю операцию – сортировку, которая в свою очередь выполняет следующую, находящуюся прямо под ней (hash join) и получает данные от нее. Hash join, чтобы вернуть данные в сортировку, должен запустить seq scan (по pg_proc) и hash (#4). И, наконец, hash, чтобы вернуть данные, должно прогнать seq scan по pg_namespace.

Очень важно понимать, что некоторые операции могут возвращать данные мгновенно или, что ещё важнее, постепенно. Например, Seq Scan. А некоторые не могут. К примеру, здесь мы видим, что Hash (#4) имеет те же начальные издержки, что и его «субоперация» seq scan для «всех строк». Это значит, что для начала операции hash (для того, чтобы она могла вернуть хоть одну строку), нужно прочитать все строки из её субопераций.

Часть о постепенном возврате строк становится особенно важна, когда вы начинаете писать функции. Давайте рассмотрим вот такую функцию:

CREATE OR REPLACE FUNCTION public.test()
 RETURNS SETOF integer
 LANGUAGE plpgsql
AS $function$
declare
    i int4;
begin
    for i in 1..3 loop
        return next i;
        perform pg_sleep(1);
    end loop;
    return;
end;
$function$;

Если вы ничего не поняли, не волнуйтесь. Функция возвращает 3 строки, каждая из которых содержит одно целое число – 1, 2 и 3. Важно то, что она засыпает на 1 секунду после возвращения каждой строки.

Это значит, что если я сделаю вот так:

select * from test();

Мне придётся ждать результатов 3 секунды.

Но как долго придётся ждать возвращения при вот таком раскладе:

select * from test() limit 1;

Давайте посмотрим:

\timing
Timing is on.
 
select * from test() limit 1;
 test 
------
    1
(1 row)
 
Time: 3005.334 ms

Те же 3 секунды. Почему? Потому что PL/pgSQL (и большинство, если не все, языков PL/*) не могут возвращать частичные результаты. Кажется, что могут – с помощью “return next" – но на самом деле все результаты хранятся в буфере и возвращаются вместе, когда исполнение функции завершается.

С другой стороны, «нормальные» операции обычно могут возвращать частичные данные. Это можно увидеть, если провести какую-нибудь банальную операцию, вроде последовательного сканирования, на непростой таблице:

create table t as
    select i as id,
        repeat('depesz', 100)::text as payload
    from generate_series(1,1000000) i;

По этой таблице видно, что:

explain analyze select * from t;
                                                   QUERY PLAN                                                   
----------------------------------------------------------------------------------------------------------------
 Seq Scan on t  (cost=0.00..185834.82 rows=10250082 width=36) (actual time=0.015..232.380 rows=1000000 loops=1)
 Total runtime: 269.666 ms
(2 rows)
 
explain analyze select * from t limit 1;
                                                  QUERY PLAN                                                  
--------------------------------------------------------------------------------------------------------------
 Limit  (cost=0.00..0.02 rows=1 width=36) (actual time=0.003..0.003 rows=1 loops=1)
   ->  Seq Scan on t  (cost=0.00..185834.82 rows=10250082 width=36) (actual time=0.003..0.003 rows=1 loops=1)
 Total runtime: 0.016 ms
(3 rows)

(пожалуйста, смотрите пока только на “Total runtime: ..")

Как вы видите, последовательное сканирование закончилось очень быстро – сразу как только удовлетворило аппетит LIMIT ровно на 1 строку.

Пожалуйста, отметьте, что здесь даже издержки (которые не являются лучшим критерием для сравнения запросов) показывают, что верхний узел (seq scan в первом запросе и limit во втором) имеет очень разные значения для возврата всех рядов – 185834.82 против 0.02.

Так что первые 4 числа для любой операции (две оценки затрат, кол-во строк и ширина) являются приблизительными. Они могут оказаться правильными, а могут и не оказаться.

Другие 4 числа, которые вы получаете, когда запускаете “EXPLAIN ANALYZE query" или “EXPLAIN ( ANALYZE on ) query", показывают реальные результаты.

Время по-прежнему указывается диапазоном, но на этот раз это реальное время. Именно столько времени PostgreSQL потратил на работу над данной операцией (в среднем, потому что он мог прогонять одну и ту же операцию множество раз). И так же, как издержки, время представлено диапазоном: время на начало выполнения операции и время на возврат всех данных. Давайте проверим этот план:

$ explain analyze select * from t limit 100;
                                                  QUERY PLAN                                                  
--------------------------------------------------------------------------------------------------------------
 Limit  (cost=0.00..9.33 rows=100 width=608) (actual time=0.008..0.152 rows=100 loops=1)
   ->  Seq Scan on t  (cost=0.00..93333.86 rows=999986 width=608) (actual time=0.007..0.133 rows=100 loops=1)
 Total runtime: 0.181 ms
(3 rows)

Как вы видите, время начала операции для Limit – 0.008 (единица измерения тут – миллисекунды). Так происходит, потому что Seq Scan (который Limit вызвал для получения данных) потребовалось 0.007мс для возвращения первой строки, а потом еще 0.001мс ушло на обработку внутри самого limit.

Далее (после возврата первой строки), limit продолжил получать данные из Seq Scan, пока не получил 100 строк. Тогда он прекратил последовательное сканирование (это произошло через 0.133мс после начала запроса) и завершился сам еще через 0.019мс.

Фактическое количество строк, как становится ясно из названия, показывает, сколько строк (в среднем) эта операция вернула. А loop показывает, сколько всего раз эта операция выполнялась.

В каком случае операция будет вызываться более одного раза? Например, в некоторых случаях с join или вложенными запросами. Это будет похоже на вот этот план.

Отметьте, что в третьей операции всего два цикла. Это значит, что данный seq scan был запущен дважды, возвращал, в среднем, 1 строку и для завершения ему, в среднем, требовалось 0.160мс. Так что общее время, затраченное на эту конкретную операцию: 2 * 0.160мс = 0.32мс (что указано в колонках exclusive/inclusive на explain.depesz.com).

Очень часто низкая производительность запроса связана с тем, что ему пришлось много раз выполнять цикл по какому-то подзапросу. Например, как здесь.

(Конечно, это не значит, что во всем виноват постгрес, выбравший такой план. Возможно, других вариантов просто не было или они оказались ещё более затратными).

В примере выше стоит обратить внимание на то, что, хотя фактическое время операции 3 – всего лишь 0.003мс, эта операция производилась более 26000 раз, что в итоге вылилось в почти 79мс затраченного времени.

Думаю, на этом теоретическая информация, необходимая для чтения explain’ов, исчерпана. Скорее всего, вы до сих пор не понимаете, что означают операции и другая информация, но, по крайней мере, теперь вы знаете, что означают числа и в чем разница между explain (который показывает затраты в абстрактных единицах измерения, основанных на приблизительной оценке случайных примеров) и explain analyze (который показывает фактическое время, число строк и время выполнения в единицах измерения, которые позволяют сравнивать разные запросы).

Как всегда, я боюсь, что пропустил много вещей, которые могут быть важны, но не попались мне на глаза, или (что ещё хуже) я посчитал их “очевидными”. Если вам покажется, что чего-то не хватает, пожалуйста, сообщите мне, и я постараюсь восполнить пробелы как можно скорее.

Но хочу также добавить, что планирую развить этот текст ещё в 2-3 публикациях, в которых больше расскажу о том:
  • какие бывают операции, как они работают и чего стоит ожидать, когда вы видите их в выводе explain;
  • что такое статистика, как Pg её получает, как её просматривать и как извлечь из неё максимум пользы.

Надеюсь, что эта статья оказалась для вас полезной. Подписывайтесь и следите за публикациями. В скором времени в блоге появятся переводы следующих серий! Как обычно, не стесняйтесь оставлять фидбек и пожелания. Самые интересные мы включим в программу предстоящего PG Day'16 Russia! :)
Поделиться публикацией

Похожие публикации

Комментарии 23

    +2
    А что если бы мы сказали постгресу, что сканирование индекса нельзя использовать ни при каких условиях?

    И это тоже давайте отключим:

    вот тут непонятно, каким образом и что он отключал?
      +2
      С самого начала вот тут: www.postgresql.org/docs/9.4/static/runtime-config-query.html
        0
        Допустим. И что из этого менял автор?
          0
          set enable_indexscan = off;
          set enable_bitmapscan = off;
            0
            Спасибо.
        0
        Да, автор не уточнил, какие именно «гайки» он крутил, что даже очень хорошо, на мой взгляд :) Неумелое кручение этих гаек может привести к поиску подземного стука потом в приложении.
          0
          Тема визардов больше ко всяким пакетам типа WAMP относится, но никак не к тем, кто использует postgres ежедневно и хочет ещё более детально в нём разбираться. Но ваш намёк на тупость понят.
            0
            Вы какие-то странные выводы из сказанного делаете, но дело ваше.
              –3
              Подождите, давайте разберём.
              Да, автор не уточнил, какие именно «гайки» он крутил

              Всё понятно, автор не уточнил
              что даже очень хорошо, на мой взгляд :) Неумелое кручение этих гаек может привести к поиску подземного стука потом в приложении.

              К чему был этот комментарий? Вы меня считаете тем, кто будет неумело им пользоваться? Тогда загляните в мой профиль. Я на хабре 9 лет, не считая сколько вообще занимаюсь айти.
                0
                Я своих собеседников никогда не считаю по-умолчанию тупыми или невежественными. Ровно поэтому, когда коллега выше упомянул ссылку на документацию, я счел, что вы, являясь, наверняка, человеком образованным и сообразительным, найдете для себя там ответ.

                Именно поэтому я оставил свой комментарий, рассчитывая на то, что вы его оцените именно с позиции человека, имеющего опыт в отрасли. Кручение «гаек» планировщика является очень опасным приемом, который в неумелых руках приводит к тому, что ранее работавшие запросы внезапно начинают дико тормозить, когда меняется статистика, собираемая базой данных, и, как следствие, выстраиваемый план запроса. То, что раньше было неэффективным и столь неэлегантным образом отключалось, внезапно стало жизненноважным для хорошей производительности.

                Мой комментарий никак не касался ваших умственных способностей, профессионального опыта или стажа пребывания на Habrahabr.
        +3
        Спасибо за «рекламу» http://explain.depesz.com — очень полезная штука.
          0
          Да, это очень достойная штука.
            +1
            Ещё такая штука есть:) http://tatiyants.com/postgres-query-plan-visualization/
              0
              Интересно! Спасибо за ссылку.
          +1
          Жги ещё, отлично что такие статьи на русском теперь появляются!
            0
            Спасибо, очень интересно. Жду продолжения.
            Интересны принципы и методики настройки параметров postgresql, для управления механизмами оценки запросов.
              0
              Статья хорошая, единственное, что вам надо тему сделать более четкую. Сложно вашу статью будет потом нагуглить :-)
                0
                Я рассчитываю на совокупность тегов и самого текста в теле статьи. Уходить от авторского названия не очень хочется.
                +3
                «Основы стоимостной оптимизации» — автор Дж. Льюис
                Вот наше все, для понимания стоимостной оптимизации (хоть там и про oracle)
                  0
                  А постгрессовский explain умеет возвращать план в табличном виде как оракл, т.е. отдельно столбец Plan operation, Owner, Object, Partition ID/Start/Stop, Cost, Cardinality, Access predicates, Filter predicates, etc?
                  Почти полный список полей
                  Describing VIEW  SYS.V_$SQL_PLAN...
                   Name                                      Null?    Type
                   ----------------------------------------- -------- ----------------------------
                   ADDRESS                                            RAW(8)
                   HASH_VALUE                                         NUMBER
                   SQL_ID                                             VARCHAR2(13)
                   PLAN_HASH_VALUE                                    NUMBER
                   CHILD_ADDRESS                                      RAW(8)
                   CHILD_NUMBER                                       NUMBER
                   TIMESTAMP                                          DATE
                   OPERATION                                          VARCHAR2(30)
                   OPTIONS                                            VARCHAR2(30)
                   OBJECT_NODE                                        VARCHAR2(40)
                   OBJECT#                                            NUMBER
                   OBJECT_OWNER                                       VARCHAR2(30)
                   OBJECT_NAME                                        VARCHAR2(30)
                   OBJECT_ALIAS                                       VARCHAR2(65)
                   OBJECT_TYPE                                        VARCHAR2(20)
                   OPTIMIZER                                          VARCHAR2(20)
                   ID                                                 NUMBER
                   PARENT_ID                                          NUMBER
                   DEPTH                                              NUMBER
                   POSITION                                           NUMBER
                   SEARCH_COLUMNS                                     NUMBER
                   COST                                               NUMBER
                   CARDINALITY                                        NUMBER
                   BYTES                                              NUMBER
                   OTHER_TAG                                          VARCHAR2(35)
                   PARTITION_START                                    VARCHAR2(64)
                   PARTITION_STOP                                     VARCHAR2(64)
                   PARTITION_ID                                       NUMBER
                   OTHER                                              VARCHAR2(4000)
                   DISTRIBUTION                                       VARCHAR2(20)
                   CPU_COST                                           NUMBER
                   IO_COST                                            NUMBER
                   TEMP_SPACE                                         NUMBER
                   ACCESS_PREDICATES                                  VARCHAR2(4000)
                   FILTER_PREDICATES                                  VARCHAR2(4000)
                   PROJECTION                                         VARCHAR2(4000)
                   TIME                                               NUMBER
                   QBLOCK_NAME                                        VARCHAR2(30)
                   REMARKS                                            VARCHAR2(4000)
                   OTHER_XML                                          CLOB
                  

                  Сокращенный пример
                  ------------------------------------------------------------------------------------------------------------------------------
                  | Id  | Operation                            | Name              | Rows  | Bytes | Cost (%CPU)| Time     |IN-OUT| PQ Distrib |
                  ------------------------------------------------------------------------------------------------------------------------------
                  |   0 | SELECT STATEMENT                     |                   |       |       |    29 (100)|          |      |            |
                  |   1 |  PX COORDINATOR                      |                   |       |       |            |          |      |            |
                  |   2 |   PX SEND QC (RANDOM)                | :TQ10000          |  8168 |    15M|    29   (0)| 00:00:01 | P->S | QC (RAND)  |
                  |   3 |    VIEW                              |                   |  8168 |    15M|    29   (0)| 00:00:01 | PCWP |            |
                  |   4 |     COLLECTION ITERATOR PICKLER FETCH| PARALLELPOPUINDEX |  8168 |       |    29   (0)| 00:00:01 | PCWP |            |
                  |   5 |      PX BLOCK ITERATOR               |                   |    33M|   388M| 31316   (1)| 00:06:16 | PCWC |            |
                  |*  6 |       TABLE ACCESS FULL              | XXXXXX            |    33M|   388M| 31316   (1)| 00:06:16 | PCWP |            |
                  ------------------------------------------------------------------------------------------------------------------------------
                  
                  Predicate Information (identified by operation id):
                  ---------------------------------------------------
                  
                     6 - access(:Z>=:Z AND :Z<=:Z)
                  

                    0
                    В табличном виде нет, но можно задать формат FORMAT { TEXT | XML | JSON | YAML } (http://www.postgresql.org/docs/9.4/static/sql-explain.html), а результат уже машинно анализировать.
                    JSON представление теоретически можно превратить в таблицу функциями postgresql.
                      0
                      Спасибо, а где можно посмотреть/почитать как получать планы со статистиками текущих выполняющихся запросов в реалтайме?
                      0
                      В виде таблички придется извращаться, но например в виде json можно для простоты.

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

                    Самое читаемое