PostgreSQLのpg_stat_statementsで遅いクエリを特定する方法|エクステンション設定から実行統計の分析まで

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOME > Linux技術 リナックスマスター.JP(Linuxマスター.JP) > Linuxtips > データーベース管理 > PostgreSQLのpg_stat_statementsで遅いクエリを特定する方法|エクステンション設定から実行統計の分析まで
「DBが重くなった」という報告が来たのに、どのSQLが原因なのかを絞り込めない。
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()で統計をリセットし、任意のタイミングから計測をやり直せる


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

なぜ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)

上記はRocky Linux 9(PostgreSQL 16)の場合だ。Ubuntu 24.04 LTS では /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

再起動後、統計を参照したいデータベースに接続してエクステンションを作成する。データベースごとに1回実行が必要だ。

# 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;

検証サーバー(db-server01, PostgreSQL 16.4)での実行例:

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)

3行目の 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は「すべて」を意味する

リセット後は直ちに統計の累積が再開される。計測前にリセットし、一定時間後に分析SQLを走らせれば、その期間だけの純粋なパフォーマンスデータが得られる。

トラブルシュート

「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();
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、Linux Master Pro Seminar ではPostgreSQLを含むデータベース運用・チューニングからサーバー構築までをハンズオン形式で体系的に学べます。20年以上・3,100名超の指導実績をぜひご活用ください。>>セミナー詳細はこちら

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。