F.30. pg_stat_statements — отслеживание статистики планирования и выполнения SQL-операторов #
Модуль pg_stat_statements предоставляет возможность отслеживать статистику планирования и выполнения сервером всех операторов SQL.
Этот модуль нужно загружать, добавив pg_stat_statements в shared_preload_libraries в файле postgresql.conf, так как ему требуется дополнительная разделяемая память. Это значит, что для загрузки или выгрузки модуля необходимо перезапустить сервер. Кроме того, для функционирования этого модуля должны вычисляться идентификаторы запросов, что происходит автоматически, когда для параметра compute_query_id задаётся значение auto или on или же загружается сторонний модуль, вычисляющий идентификаторы запросов.
Когда модуль pg_stat_statements активен, он отслеживает статистику по всем базам данных на сервере. Для получения и обработки этой статистики модуль pg_stat_statements предоставляет представления pg_stat_statements и pg_stat_statements_info, а также вспомогательные функции pg_stat_statements_reset и pg_stat_statements. Эти объекты не доступны глобально, но их можно установить в определённой базе данных, выполнив команду CREATE EXTENSION pg_stat_statements.
F.30.1. Представление pg_stat_statements #
Статистика, собираемая модулем, выдаётся через представление с именем pg_stat_statements. Это представление содержит отдельные строки для каждой комбинации идентификатора базы данных, идентификатора пользователя, идентификатора запроса и признака верхнего уровня (но в количестве, не превышающем максимальное число различных операторов, которые может отслеживать модуль). Столбцы представления показаны в Таблице F.21.
Таблица F.21. Столбцы pg_stat_statements
Тип столбца Описание |
|---|
OID пользователя, выполнявшего оператор |
OID базы данных, в которой выполнялся оператор |
True, если данный запрос выполнялся на верхнем уровне (всегда true, если для параметра |
Хеш-код для выявления одинаковых нормализованных запросов |
Текст, представляющий оператор |
Число операций планирования этого оператора (если включён параметр |
Общее время, затраченное на планирование этого оператора в миллисекундах (если включён параметр |
Минимальное время, затраченное на планирование этого оператора, в миллисекундах. В столбце отобразится 0 при отключённом параметре |
Максимальное время, затраченное на планирование этого оператора, в миллисекундах. В столбце отобразится 0 при отключённом параметре |
Среднее время, затраченное на планирование этого оператора в миллисекундах (если включён параметр |
Стандартное отклонение времени, затраченного на планирование этого оператора в миллисекундах (если включён параметр |
Число выполнений |
Общее время, затраченное на выполнение оператора, в миллисекундах |
Минимальное время, затраченное на выполнение оператора, в миллисекундах. В столбце отобразится 0, если оператор выполняется впервые после очистки статистики функцией |
Максимальное время, затраченное на выполнение оператора, в миллисекундах. В столбце отобразится 0, если оператор выполняется впервые после очистки статистики функцией |
Среднее время, затраченное на выполнение оператора, в миллисекундах |
Стандартное отклонение времени, затраченного на выполнение оператора, в миллисекундах |
Общее число строк, полученных или затронутых оператором |
Общее число попаданий разделяемых блоков в кеш для данного оператора |
Общее число разделяемых блоков, прочитанных данным оператором |
Общее число разделяемых блоков, «загрязнённых» данным оператором |
Общее число разделяемых блоков, записанных данным оператором |
Общее число попаданий локальных блоков в кеш для данного оператора |
Общее число локальных блоков, прочитанных данным оператором |
Общее число локальных блоков, «загрязнённых» данным оператором |
Общее число локальных блоков, записанных данным оператором |
Общее число временных блоков, прочитанных данным оператором |
Общее число временных блоков, записанных данным оператором |
Общее время, затраченное оператором на чтение разделяемых блоков, в миллисекундах (если включён track_io_timing, иначе ноль) |
Общее время, затраченное оператором на запись разделяемых блоков, в миллисекундах (если включён track_io_timing, иначе ноль) |
Общее время, затраченное оператором на чтение локальных блоков, в миллисекундах (если включён track_io_timing, иначе ноль) |
Общее время, затраченное оператором на запись локальных блоков, в миллисекундах (если включён track_io_timing, иначе ноль) |
Общее время, затраченное оператором на чтение блоков временных файлов, в миллисекундах (если включён track_io_timing, или ноль в противном случае) |
Общее время, затраченное оператором на запись блоков временных файлов, в миллисекундах (если включён track_io_timing, или ноль в противном случае) |
Общее число записей WAL, сгенерированных при выполнении оператора |
Общее число образов полных страниц в WAL, сгенерированных при выполнении оператора |
Общий объём WAL, сгенерированный при выполнении оператора, в байтах |
Общее число функций, скомпилированных в JIT-код при выполнении данного оператора |
Общее время, затраченное на компиляцию JIT-кода при выполнении оператора, в миллисекундах |
Сколько раз встраивались функции |
Общее время, затраченное на встраивание функций при выполнении оператора, в миллисекундах |
Число JIT-оптимизаций для данного оператора |
Общее время, затраченное на JIT-оптимизацию при выполнении оператора, в миллисекундах |
Сколько раз выдавался код |
Общее время, затраченное на выдачу кода при выполнении оператора, в миллисекундах |
Общее число функций преобразования кортежей, скомпилированных в JIT-код при выполнении данного оператора |
Общее время, затраченное оператором на компилирование функций преобразования кортежей в JIT-код, в миллисекундах |
Время начала сбора статистики по этому оператору |
Время начала сбора статистики минимальных/максимальных значений по этому оператору (столбцы |
По соображениям безопасности только суперпользователям и ролям с правами роли pg_read_all_stats разрешено видеть текст SQL и queryid запросов, выполняемых другими пользователями. Однако другие пользователи могут видеть статистику, если это представление установлено в их базу данных.
Планируемые запросы (то есть SELECT, INSERT, UPDATE, DELETE, MERGE) и служебные команды объединяются в одну запись в pg_stat_statements, когда они имеют идентичные структуры запросов согласно внутреннему вычисленному хешу. Обычно два запроса будут считаться равными при таком сравнении, если они семантически равнозначны, не считая значений констант, фигурирующих в запросе.
Примечание
Нижеприведённые соображения о замене констант и queryid применимы, только если включён compute_query_id. Если же для вычисления queryid вы используете встроенный модуль, обратитесь к его документации.
Когда значение константы игнорируется в целях сравнения запроса с другими запросами, эта константа заменяется в выводе pg_stat_statements обозначением параметра, например, $1. В остальном этот вывод содержит текст первого запроса, хеш которого равнялся значению queryid, связанному с записью в pg_stat_statements.
В pg_stat_statements запросы, для которых может быть применена нормализация, могут отображаться со значениями констант, особенно при большом числе отбрасываемых записей. Чтобы снизить вероятность отбрасывания записей, можно задать более высокое значение параметра pg_stat_statements.max. Представление pg_stat_statements_info, описанное ниже в разделе Подраздел F.30.2, предоставляет статистику об отброшенных записях.
В некоторых случаях запросы с визуально различными текстами могут быть объединены в одну запись pg_stat_statements. Обычно это происходит только для семантически равнозначных запросов, но есть небольшая вероятность, что из-за наложений хеша несвязанные запросы могут оказаться объединёнными в одной записи. (Однако это невозможно для запросов, принадлежащих разным пользователям баз данных.)
Так как значение хеша queryid вычисляется по представлениям запроса на стадии после разбора, возможна и обратная ситуация: запросы с одинаковым текстом могут оказаться в разных записях, если они получили различные представления по разным причинам, например, из-за изменения search_path.
Потребители статистики pg_stat_statements могут пожелать использовать в качестве более стабильного и надёжного идентификатора для каждой записи не текст запроса, а queryid (возможно, в сочетании с dbid и userid). Однако важно понимать, что стабильность значения хеша queryid гарантируется с ограничениями. Так как этот идентификатор получается из дерева запроса после анализа, его значение будет, помимо прочего, зависеть от внутренних идентификаторов объектов, фигурирующих в этом представлении. С этим связано несколько неинтуитивных следствий. Например, pg_stat_statements будет считать два одинаково выглядящих запроса разными, если они обращаются к таблице, которая была удалена, а затем воссоздана между этими запросами. Результат хеширования также чувствителен к различиям в машинной архитектуре и другим особенностям платформы. Более того, не стоит рассчитывать на то, что queryid будет оставаться неизменным при обновлении основных версий PostgreSQL.
Можно ожидать, что два сервера, участвующие в репликации на основе воспроизведения физического WAL, будут иметь одинаковые queryid для одного запроса. Однако схемы с логической репликацией не гарантируют сохранения идентичности реплик во всех имеющих значение деталях, так что queryid не будет полезным идентификатором для накопления показателей стоимости по набору логических реплик. В случае сомнений в том или ином подходе рекомендуется непосредственно протестировать его.
Как правило, можно предположить, что значения queryid не меняются между корректирующими релизами PostgreSQL при условии, что экземпляры работают на одной и той же архитектуре и детали метаданных каталога совпадают. Совместимость между корректирующими релизами может быть нарушена только в крайнем случае.
Обозначения параметров, применяемые для замены констант в представляющем запросы тексте, нумеруются, начиная со следующего за последним параметром $n в исходном тексте запроса, или с $1 в отсутствие параметров в нём. Стоит отметить, что в некоторых случаях на эту нумерацию могут влиять скрытые символы параметров. Например, PL/pgSQL применяет такие символы для добавления в запросы значений локальных переменных функций, так что оператор PL/pgSQL вида SELECT i + 1 INTO j будет представлен в тексте как SELECT i + $2.
Текст, представляющий запрос, сохраняется во внешнем файле на диске и не занимает разделяемую память. Поэтому даже очень объёмный текст запроса может быть сохранён успешно. Однако если в файле накапливается много длинных текстов запросов, он может вырасти до неудобоваримого размера. В качестве решения этой проблемы, pg_stat_statements может решить стереть текст запросов, и в результате во всех существующих записях в представлении pg_stat_statements в поле query окажутся значения NULL, хотя статистика, связанная с каждым queryid будет сохранена. Если это происходит и мешает анализу, возможно, стоит уменьшить pg_stat_statements.max для предотвращения таких ситуаций.
Показатели plans и calls не обязательно должны совпадать, так как статистика планирования и выполнения обновляется в конце соответствующей фазы и только при успешном завершении этой фазы. Например, если для оператора успешно выполнилось планирование, но во время выполнения произошла ошибка, изменится только статистика планирования. Если же планирование пропускается по причине использования кешированного плана, увеличивается только счётчик выполнения.
F.30.2. Представление pg_stat_statements_info #
Статистика самого модуля pg_stat_statements собирается и выдаётся через представление с именем pg_stat_statements_info. Это представление содержит только одну строку. Столбцы представления показаны в Таблице F.22.
Таблица F.22. Столбцы pg_stat_statements_info
Тип столбца Описание |
|---|
Сколько всего раз в |
Время последнего сброса всех статистических данных в представлении |
F.30.3. Функции #
-
pg_stat_statements_reset(userid Oid, dbid Oid, queryid bigint, minmax_only boolean) returns timestamp with time zone Функция
pg_stat_statements_resetочищает всю статистику, собранную к этому времени модулемpg_stat_statementsдля заданного пользователя (userid), базы данных (dbid) и запроса (queryid). В случае отсутствия одного из параметров для него подразумевается нулевое значение (неприменимое ограничение) и очищается статистика, соответствующая другим параметрам. Если никакой параметр не задан или все параметры имеют нулевое значение (неприменимое), очищается вся статистика. Вместе с очисткой всей статистики вpg_stat_statementsтакже сбрасывается статистика вpg_stat_statements_info. Когда для параметраminmax_onlyустановлено значениеtrue, сбрасываются значения только минимального и максимального времени планирования и выполнения оператора (т. е. значения столбцовmin_plan_time,max_plan_time,min_exec_timeиmax_exec_time). Значениеminmax_onlyпо умолчанию —false. Время последнего сброса минимальных/максимальных значений можно увидеть в столбцеminmax_stats_sinceпредставленияpg_stat_statementsили в столбцеstats_resetпредставленияpg_stat_statements_info, если соответствующий сброс значений был действительно выполнен. По умолчанию эту функцию могут выполнять только суперпользователи. Другим пользователям можно дать доступ к ней, используяGRANT.-
pg_stat_statements(showtext boolean) returns setof record Представление
pg_stat_statementsреализовано на базе функции, которая тоже называетсяpg_stat_statements. Клиенты могут вызывать функциюpg_stat_statementsнепосредственно, и могут указатьshowtext := falseи получить результат без текста запроса (то есть, выходной аргумент (OUT), соответствующий столбцу представленияquery, будет содержать NULL). Эта возможность предназначена для поддержки внешних инструментов, для которых желательно избежать издержек, связанных с получением текстов запросов неопределённой длины. Такие инструменты могут кешировать текст первого запроса, который они получат самостоятельно, как это и делаетpg_stat_statements, а затем запрашивать тексты запросов только при необходимости. Так как сервер сохраняет тексты запросов в файле, этот подход сокращает объём физического ввода/вывода, порождаемого при постоянном обращении к даннымpg_stat_statements.
F.30.4. Параметры конфигурации #
pg_stat_statements.max(integer)Параметр
pg_stat_statements.maxзадаёт максимальное число операторов, отслеживаемых модулем (то есть, максимальное число строк в представленииpg_stat_statements). Когда на обработку поступает больше, чем заданное число различных операторов, информация о редко выполняемых операторах отбрасывается. В представленииpg_stat_statements_infoможно увидеть, сколько раз это происходило. Значение по умолчанию — 5000. Этот параметр можно задать только при запуске сервера.pg_stat_statements.track(enum)Параметр
pg_stat_statements.trackопределяет, какие операторы будут отслеживаться модулем. Со значениемtopотслеживаются операторы верхнего уровня (те, что непосредственно выполняются клиентами), со значениемallтакже отслеживаются вложенные операторы (например, операторы, вызываемые внутри функций), а значениеnoneполностью отключает сбор статистики по операторам. Значение по умолчанию —top. Изменять этот параметр могут только суперпользователи.pg_stat_statements.track_utility(boolean)Параметр
pg_stat_statements.track_utilityопределяет, будет ли этот модуль отслеживать служебные команды. Служебными командами считаются команды, отличные отSELECT,INSERT,UPDATE,DELETEиMERGE. Значение по умолчанию —on(вкл.). Изменить этот параметр могут только суперпользователи.pg_stat_statements.track_planning(boolean)Параметр
pg_stat_statements.track_planningопределяет, будет ли этот модуль отслеживать операции планирования и их длительность. Включение этого параметра может привести к заметному снижению производительности, особенно когда во множестве одновременных сеансов выполняются операторы с одинаковой структурой запросов, в результате чего эти сеансы пытаются одновременно изменить одни и те же записи вpg_stat_statements. Значение по умолчанию —off(выкл.). Изменить этот параметр могут только суперпользователи.pg_stat_statements.save(boolean)Параметр
pg_stat_statements.saveопределяет, должна ли статистика операторов сохраняться после перезагрузки сервера. Если он отключён (имеет значениеoff), статистика не сохраняется при остановке сервера и не перезагружается при запуске. Значение по умолчанию —on(вкл.). Этот параметр можно задать только вpostgresql.confили в командной строке сервера.
Этому модулю требуется дополнительная разделяемая память в объёме, пропорциональном pg_stat_statements.max. Заметьте, что эта память будет занята при загрузке модуля, даже если pg_stat_statements.track имеет значение none.
Эти параметры должны задаваться в postgresql.conf. Обычное использование выглядит так:
# postgresql.conf shared_preload_libraries = 'pg_stat_statements' compute_query_id = on pg_stat_statements.max = 10000 pg_stat_statements.track = all
F.30.5. Пример вывода #
bench=# SELECT pg_stat_statements_reset();
$ pgbench -i bench
$ pgbench -c10 -t300 bench
bench=# \x
bench=# SELECT query, calls, total_exec_time, rows, 100.0 * shared_blks_hit /
nullif(shared_blks_hit + shared_blks_read, 0) AS hit_percent
FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 5;
-[ RECORD 1 ]---+--------------------------------------------------------------------
query | UPDATE pgbench_branches SET bbalance = bbalance + $1 WHERE bid = $2
calls | 3000
total_exec_time | 25565.855387
rows | 3000
hit_percent | 100.0000000000000000
-[ RECORD 2 ]---+--------------------------------------------------------------------
query | UPDATE pgbench_tellers SET tbalance = tbalance + $1 WHERE tid = $2
calls | 3000
total_exec_time | 20756.669379
rows | 3000
hit_percent | 100.0000000000000000
-[ RECORD 3 ]---+--------------------------------------------------------------------
query | copy pgbench_accounts from stdin
calls | 1
total_exec_time | 291.865911
rows | 100000
hit_percent | 100.0000000000000000
-[ RECORD 4 ]---+--------------------------------------------------------------------
query | UPDATE pgbench_accounts SET abalance = abalance + $1 WHERE aid = $2
calls | 3000
total_exec_time | 271.232977
rows | 3000
hit_percent | 98.8454011741682975
-[ RECORD 5 ]---+--------------------------------------------------------------------
query | alter table pgbench_accounts add primary key (aid)
calls | 1
total_exec_time | 160.588563
rows | 0
hit_percent | 100.0000000000000000
bench=# SELECT pg_stat_statements_reset(0,0,s.queryid) FROM pg_stat_statements AS s
WHERE s.query = 'UPDATE pgbench_branches SET bbalance = bbalance + $1 WHERE bid = $2';
bench=# SELECT query, calls, total_exec_time, rows, 100.0 * shared_blks_hit /
nullif(shared_blks_hit + shared_blks_read, 0) AS hit_percent
FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 5;
-[ RECORD 1 ]---+--------------------------------------------------------------------
query | UPDATE pgbench_tellers SET tbalance = tbalance + $1 WHERE tid = $2
calls | 3000
total_exec_time | 20756.669379
rows | 3000
hit_percent | 100.0000000000000000
-[ RECORD 2 ]---+--------------------------------------------------------------------
query | copy pgbench_accounts from stdin
calls | 1
total_exec_time | 291.865911
rows | 100000
hit_percent | 100.0000000000000000
-[ RECORD 3 ]---+--------------------------------------------------------------------
query | UPDATE pgbench_accounts SET abalance = abalance + $1 WHERE aid = $2
calls | 3000
total_exec_time | 271.232977
rows | 3000
hit_percent | 98.8454011741682975
-[ RECORD 4 ]---+--------------------------------------------------------------------
query | alter table pgbench_accounts add primary key (aid)
calls | 1
total_exec_time | 160.588563
rows | 0
hit_percent | 100.0000000000000000
-[ RECORD 5 ]---+--------------------------------------------------------------------
query | vacuum analyze pgbench_accounts
calls | 1
total_exec_time | 136.448116
rows | 0
hit_percent | 99.9201915403032721
bench=# SELECT pg_stat_statements_reset(0,0,0);
bench=# SELECT query, calls, total_exec_time, rows, 100.0 * shared_blks_hit /
nullif(shared_blks_hit + shared_blks_read, 0) AS hit_percent
FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 5;
-[ RECORD 1 ]---+-----------------------------------------------------------------------------
query | SELECT pg_stat_statements_reset(0,0,0)
calls | 1
total_exec_time | 0.189497
rows | 1
hit_percent |
-[ RECORD 2 ]---+-----------------------------------------------------------------------------
query | SELECT query, calls, total_exec_time, rows, $1 * shared_blks_hit / +
| nullif(shared_blks_hit + shared_blks_read, $2) AS hit_percent+
| FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT $3
calls | 0
total_exec_time | 0
rows | 0
hit_percent |F.30.6. Авторы #
Такахиро Итагаки <itagaki.takahiro@oss.ntt.co.jp>. Нормализацию запросов добавил Питер Гейган <peter@2ndquadrant.com>.