MySQLにはスロークエリログ(
slow_query_log)がある。しかしPostgreSQLには同等の仕組みが標準では組み込まれておらず、log_min_duration_statementでログ出力はできても「同じパターンのSQLが合計で何秒かかっているか」がひと目でわかるビューは存在しない。この記事では、PostgreSQLの公式エクステンション pg_stat_statements を使って、実行時間の長いクエリや頻繁に呼ばれているクエリを特定する方法を解説する。Rocky Linux 9 / Ubuntu 24.04 LTSのPostgreSQL 16で動作確認した実機ベースの手順だ。
この記事のポイント
・pg_stat_statementsはPostgreSQLの実行SQLを正規化して統計集計する公式エクステンション
・postgresql.confに1行追加して再起動するだけで有効化でき、追加パッケージは不要
・累積実行時間・平均実行時間・バッファキャッシュヒット率でクエリをランキングできる
・pg_stat_statements_reset()で統計をリセットし、任意のタイミングから計測をやり直せる
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜpg_stat_statementsが必要なのか
PostgreSQLにもlog_min_duration_statement という設定がある。これを使えば「Xミリ秒以上かかったSQLをログファイルに書く」という動作は実現できる。しかし実際の運用では次の課題が出やすい。・ログファイルが膨大になり、解析ツールなしでは目視で追いきれない
・
WHERE user_id = 1 と WHERE user_id = 9999 のような同型SQLが個別行で散らばり、「同パターンのSQLが合計何秒かかっているか」が把握できない・しきい値を下げると出力量が爆発し、しきい値を上げると見落としが増える
pg_stat_statements は上記の問題を解消するために設計された公式エクステンションだ。クエリのリテラル値を省いた形(
WHERE user_id = $1)に正規化したうえで統計を累積するため、同型クエリを1行にまとめてカウントできる。実行回数・合計時間・最大時間・平均時間・バッファアクセス数まで一括取得できるため、チューニングの糸口を見つけやすい。pg_stat_statements はPostgreSQL本体に同梱されており、追加パッケージのインストールは原則不要だ。
pg_stat_statementsを有効化する手順
1. postgresql.confにshared_preload_librariesを追加する
pg_stat_statements はPostgreSQLの起動前にメモリにロードしておく必要がある。設定ファイルの場所をまず確認しよう。# 設定ファイルの場所を確認する $ sudo -u postgres psql -c "SHOW config_file;" config_file ----------------------------------------- /var/lib/pgsql/16/data/postgresql.conf (1 row)
/etc/postgresql/16/main/postgresql.conf になる。設定ファイルを開き、
shared_preload_libraries の行を追加または変更する。# /var/lib/pgsql/16/data/postgresql.conf の編集箇所 # 既存値がある場合はカンマ区切りで追記する(例: 'pg_stat_statements,auto_explain') shared_preload_libraries = 'pg_stat_statements' # 収集する最大クエリ数(デフォルト: 5000) pg_stat_statements.max = 10000 # 追跡対象: top(直接実行のみ)/ all(関数・ストアドプロシージャ内部も含む) # 注意: all に設定すると関数・プロシージャ内のSQLも追跡するため、サービス規模によっては # オーバーヘッドが増加する。まずは top で運用し、必要に応じて変更するのが安全だ pg_stat_statements.track = top
track = all に設定するのは避けること。all は関数・ストアドプロシージャ内部のSQLまで追跡するため、トラフィックが多いシステムでは統計収集のオーバーヘッドが無視できない場合がある。まずは top で運用し、より詳細な分析が必要になったタイミングで切り替える方が安全だ。2. PostgreSQLを再起動してエクステンションを作成する
shared_preload_libraries の変更を反映するには pg_reload_conf() ではなく、サービス全体の再起動が必要だ。# Rocky Linux 9 / RHEL系(サービス名に版数が入る) $ sudo systemctl restart postgresql-16 # Ubuntu 24.04 LTS $ sudo systemctl restart postgresql
# postgresユーザーで対象データベースに接続してエクステンションを作成する $ sudo -u postgres psql -d myapp psql (16.4) Type "help" for help. myapp=# CREATE EXTENSION IF NOT EXISTS pg_stat_statements; CREATE EXTENSION myapp=# \q
3. 有効化の確認
$ sudo -u postgres psql -d myapp -c "\dx pg_stat_statements" List of installed extensions Name | Version | Schema | Description --------------------+---------+------------+---------------------------------------- pg_stat_statements | 1.10 | public | track planning and execution statistics (1 row)
pg_stat_statementsの主要カラム
pg_stat_statements ビューの中でチューニングに特によく使うカラムを整理する。| カラム名 | 内容 |
|---|---|
query |
リテラルを正規化したクエリ文字列(値部分は $1/$2 のプレースホルダー形式になる) |
calls |
累積実行回数 |
total_exec_time |
累積実行時間(ミリ秒)。PostgreSQL 13以前は total_time という名称だった |
mean_exec_time |
1回あたりの平均実行時間(ミリ秒) |
max_exec_time |
最大実行時間(ミリ秒)。スパイクの有無の確認に使う |
shared_blks_hit |
バッファキャッシュからのヒット数 |
shared_blks_read |
ディスクから読み込んだブロック数 |
shared_blks_hit / (shared_blks_hit + shared_blks_read) がバッファキャッシュヒット率だ。この値が低いクエリはディスクI/Oが多く、shared_buffers の増量やインデックスの追加が有効なことが多い。PostgreSQL 14以降は total_time が total_exec_time(実行フェーズ)と total_plan_time(プランニングフェーズ)に分離されている。バージョンが古い場合は \d pg_stat_statements でカラム名を確認してから使うこと。遅いクエリを特定する分析SQL
1. 累積実行時間のワースト10を抽出する
「サービス全体でどのSQLが最も時間を消費しているか」を調べるには合計実行時間でソートする。SELECT left(query, 80) AS query_abbr, calls, round(total_exec_time::numeric, 2) AS total_ms, round(mean_exec_time::numeric, 2) AS mean_ms, round(max_exec_time::numeric, 2) AS max_ms FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 10;
query_abbr | calls | total_ms | mean_ms | max_ms ---------------------------------------+-------+------------+----------+---------- SELECT o.*, u.name FROM orders o JOIN | 8423 | 124538.47 | 14.79 | 312.45 UPDATE sessions SET last_seen_at=$1 W | 52110 | 98721.33 | 1.89 | 18.22 SELECT COUNT(*) FROM logs WHERE creat | 234 | 45892.11 | 196.11 | 2344.00 INSERT INTO access_log (user_id, acti | 103201| 41230.55 | 0.40 | 12.34 (4 rows)
SELECT COUNT(*) FROM logs は呼び出し回数こそ少ないが、平均196ms・最大2344msと際立って重い。大量行のシーケンシャルスキャンかインデックス不足が疑われる。この種のクエリは EXPLAIN (ANALYZE, BUFFERS) で実行計画を確認してインデックスを追加するか、対象テーブルのパーティショニングを検討する。2. 平均実行時間のワースト10(呼び出し回数フィルタ付き)
呼び出し回数が1~2回しかないSQLが上位に出ることがある。バッチや管理コマンドの可能性が高く、チューニング優先度は低い。呼び出し回数のしきい値(下記は100回)を設けて絞り込む。SELECT left(query, 80) AS query_abbr, calls, round(mean_exec_time::numeric, 2) AS mean_ms, round(max_exec_time::numeric, 2) AS max_ms FROM pg_stat_statements WHERE calls >= 100 ORDER BY mean_exec_time DESC LIMIT 10;
3. バッファキャッシュヒット率が低いクエリを探す
ヒット率が低いクエリはディスクI/Oが多く(インデックスが効いていないかshared_buffers が不足している可能性が高い)、パフォーマンス改善の余地が大きい。SELECT left(query, 80) AS query_abbr, calls, shared_blks_hit, shared_blks_read, round( 100.0 * shared_blks_hit / NULLIF(shared_blks_hit + shared_blks_read, 0), 1 ) AS hit_rate_pct FROM pg_stat_statements WHERE shared_blks_hit + shared_blks_read > 0 AND calls >= 10 ORDER BY hit_rate_pct ASC LIMIT 10;
hit_rate_pct が80%を下回るクエリは重点チェック対象だ。問題のあるクエリが見つかったら、EXPLAIN (ANALYZE, BUFFERS) でインデックスが使われているか確認し、不足しているインデックスを追加するか、postgresql.confの shared_buffers を調整する。LinuxサーバーのPostgreSQL運用を体系的に学びたい方には、Linux Master Pro Seminar でハンズオン形式でチューニングの実務を学ぶ選択肢もある。統計をリセットして計測をやり直す
pg_stat_statements の統計は累積値だ。「本番リリース後の1時間だけを計測したい」「デプロイ前後で比較したい」といった場合には、計測開始前にリセットをかける。-- データベース全体の統計をリセットする(スーパーユーザー権限が必要) myapp=# SELECT pg_stat_statements_reset(); pg_stat_statements_reset -------------------------- (1 row) -- PostgreSQL 12以降: 第3引数にqueryidを指定して特定クエリだけリセットすることも可能 -- SELECT pg_stat_statements_reset(0, 0, 0); -- 引数0は「すべて」を意味する
トラブルシュート
「relation "pg_stat_statements" does not exist」が出る
エクステンションが接続先データベースに作成されていない場合に出るエラーだ。接続しているデータベースでCREATE EXTENSION pg_stat_statements; を実行する(データベースごとに作成が必要)。ERROR: relation "pg_stat_statements" does not exist LINE 1: SELECT * FROM pg_stat_statements LIMIT 5; ^ -- 対処: 接続中のデータベースでエクステンションを作成する myapp=# CREATE EXTENSION pg_stat_statements; CREATE EXTENSION
shared_preload_librariesの変更が反映されない
pg_reload_conf() では shared_preload_libraries の変更は反映できない。systemctl restart postgresql-16(Ubuntuは postgresql)でサービスを再起動する必要がある。現在の値は SHOW shared_preload_libraries; で確認できる。-- 現在ロードされているライブラリを確認する $ sudo -u postgres psql -c "SHOW shared_preload_libraries;" shared_preload_libraries -------------------------- pg_stat_statements (1 row)
統計が蓄積されない(calls=0が続く)
pg_stat_statements.track = none になっている場合や、PostgreSQLを再起動前に開いた古いセッションからSQLを実行した場合は統計が集計されないことがある。設定を確認したうえで再接続し、対象のSQLを実行し直す。$ sudo -u postgres psql -c "SHOW pg_stat_statements.track;" pg_stat_statements.track -------------------------- top (1 row)
本記事のまとめ
| やりたいこと | コマンド・設定 |
|---|---|
| pg_stat_statementsを有効化する | shared_preload_libraries = 'pg_stat_statements'(postgresql.confに追加後、再起動) |
| エクステンションを作成する | CREATE EXTENSION IF NOT EXISTS pg_stat_statements; |
| 有効化を確認する | \dx pg_stat_statements |
| 累積実行時間ワーストを確認する | SELECT ... FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 10; |
| 平均実行時間ワーストを確認する(多数呼び出し) | SELECT ... FROM pg_stat_statements WHERE calls >= 100 ORDER BY mean_exec_time DESC; |
| キャッシュヒット率が低いクエリを探す | SELECT ..., 100.0 * shared_blks_hit / NULLIF(...) AS hit_rate_pct ... ORDER BY hit_rate_pct ASC; |
| 統計をリセットする | SELECT pg_stat_statements_reset(); |
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:pgBouncerでPostgreSQLの接続プールを設定する手順|最大接続数の削減とpgbouncer.iniの書き方
- この記事の属するカテゴリ:データーベース管理へ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます