<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>pg_hint_plan &#8211; SRA OSS Tech Blog</title>
	<atom:link href="https://www.sraoss.co.jp/tech-blog/tag/pg_hint_plan/feed/index.xml" rel="self" type="application/rss+xml" />
	<link>https://www.sraoss.co.jp/tech-blog/</link>
	<description>OSS専門の技術ブログ。PostgreSQL、Zabbixを中心に10種類以上のOSSの解説、アップデート情報、運用ヒントを提供。</description>
	<lastBuildDate>Fri, 30 Oct 2026 08:42:53 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.3</generator>

<image>
	<url>https://www.sraoss.co.jp/tech-blog/wp-content/uploads/2018/07/blog-icon-150x150.png</url>
	<title>pg_hint_plan &#8211; SRA OSS Tech Blog</title>
	<link>https://www.sraoss.co.jp/tech-blog/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>PostgreSQL 性能問題の調査に役立つパラメータ設定と拡張</title>
		<link>https://www.sraoss.co.jp/tech-blog/pgsql/perf-troubleshoot-params-extension/</link>
		
		<dc:creator><![CDATA[北山 貴広 (Takahiro Kitayama)]]></dc:creator>
		<pubDate>Fri, 30 Oct 2026 07:04:01 +0000</pubDate>
				<category><![CDATA[PostgreSQL]]></category>
		<category><![CDATA[ANALYZE]]></category>
		<category><![CDATA[pg_hint_plan]]></category>
		<category><![CDATA[pg_stat_statements]]></category>
		<category><![CDATA[pg_statsinfo]]></category>
		<category><![CDATA[PostgreSQL 18.6]]></category>
		<category><![CDATA[VACUUM]]></category>
		<guid isPermaLink="false">https://www.sraoss.co.jp/tech-blog/?p=21318</guid>

					<description><![CDATA[PostgreSQLサポートでは「SELECTが突然遅くなった」「データベースが全体的に遅い時間帯がある」「バッチの実行時間が5分から2時間になった」などの問い合わせをいただきます。 その際、調査をするための情報として、 ...]]></description>
										<content:encoded><![CDATA[<p>PostgreSQLサポートでは「SELECTが突然遅くなった」「データベースが全体的に遅い時間帯がある」「バッチの実行時間が5分から2時間になった」などの問い合わせをいただきます。<br />
その際、調査をするための情報として、現在の設定やログ、SQLが遅延した時に実行時間とSQL文をログに出すとか実行計画もあわせて出すとかをしておけば、遅延が発生した時や通常とは異なる状況の時に、通常とは何が違うのか、遅延時の実行計画がどうだったのかなどを調べることができます。</p>
<p>そこで、本記事では性能問題発生時の調査に必要な情報を取得するための設定や、性能遅延時の分析に有用な拡張を紹介します。</p>
<p><span id="more-21318"></span></p>
<p>パラメータ設定の対象メジャーバージョンはPostgreSQL 10以上としています。なお、検証例で使用しているPostgreSQLは PostgreSQL 18.6、OSはRocky Linux 9.5です。</p>
<h3>OSについて</h3>
<p>現在、PostgreSQLを使う時のOSの選択肢としてよくLinuxにするかWindowsにするかの選択があります。<br />
新規構築でOS選択の自由度がある場合は、Linuxを推奨します。</p>
<p>主な理由は下記の三つです。</p>
<ul>
<li>同一スペックのサーバであればLinuxのほうが性能がよい</li>
<li>Linuxでは利用可能な拡張や周辺ツールの選択肢がWindowsより豊富である(今回紹介するpg_statsinfoもWindowsでは非対応)</li>
<li>トラブル発生時の調査がLinuxの方が容易/Windowsでは困難なことが多い</li>
</ul>
<p>Linuxでは性能面・運用面のメリットが多く、利用可能な拡張も豊富であるため、トラブル調査も進めやすくなります。</p>
<h2>PostgreSQLログ関連パラメータ</h2>
<p>PostgreSQLログに関してお勧めのパラメータ設定を紹介します。</p>
<h3>ログ出力先</h3>
<p>ログの出力先(<code>log_destination</code>)は、stderrがお勧めです。syslog を使う必要がある場合でも、stderrも含めて&#8217;stderr, syslog&#8217;に設定しましょう。syslog のみ設定するのは絶対に避けましょう。<br />
stderrを含める時は、<code>logging_collector</code>もonにしておく必要があります。</p>
<pre>log_destination = 'stderr'
logging_collector = on
</pre>
<p>stderr をお勧めする理由は、<code>archive_command</code>パラメータで設定したcpコマンドが実行される際の標準エラー出力もPostgreSQLログに含まれるからです。例えばコピー先のディスクの空き容量が無くcpコマンドが失敗した時のエラーメッセージが該当します。syslog の場合は、このメッセージはsyslogに出力されずに、<code>log_destination</code>で設定されたファイルに出力されてしまいます。</p>
<p>また、syslogでは高負荷時などにPostgreSQLからのメッセージを取りこぼす可能性があるのもsyslogを避ける理由です。PostgreSQLマニュアルにも「syslogは、もし書き込みができなかったときメッセージの廃棄を選びます。」との記述があります。またsyslog側の機能としてレート制限があり、制限値以上のメッセージが到着した場合に<code>"NNN messages lost due to rate-limiting"</code>というメッセージでNNN行失われたことが伝えられます。高負荷時にこそPostgreSQLが出力するログを確認したいのに、syslogではメッセージが大量に出力される高負荷時にsyslog側でメッセージが省略されることが多々ありますので、syslogを使う必要がある場合は必ずstderrでもログを取得するようにしましょう。</p>
<h3>チェックポイントの実行と自動VACUUM/ANALYZEの実行</h3>
<p>PostgreSQLなどのデータベースでは一定間隔ごとにチェックポイントが実行されます。チェックポイントは<code>shared_buffers</code>で設定された共有バッファ上で変更されたがファイルシステム上のデータベースファイルにはまだ書き込まれていない、いわゆるダーティーバッファをすべてデータベースファイルに書き出すことで、チェックポイント終了時点のすべてのデータベースファイルで一貫性が取れている状態にすることが目的の処理です。<br />
ダーティーバッファをすべてファイルに書き出す処理ですので、Disk I/Oを使用します。SQL実行などのクライアント側の処理への影響は程度の差はあれあります。SQL実行で何らかの影響を受けたかどうかの調査で、そのタイミングでチェックポイントが実行されていたかどうかを確認するために、チェックポイント実行(<code>log_checkpoints</code>)をPostgreSQLログに出力することは有用です。</p>
<pre>log_checkpoints = on
# PostgreSQL 14までデフォルトoff、PostgreSQL 15からデフォルトon
</pre>
<p>自動VACUUMや自動ANALYZEの実行もチェックポイントと同様に、Disk I/Oを使用します。PostgreSQLログに出力する目的はチェックポイントと同様に、SQL実行時に自動VACUUMや自動ANALYZEが実行されていたかどうかを調査する観点もありますが、もう一つの観点としてSQLの実行計画が突然変わった時に、その直前で実行計画の策定に影響するオプティマイザ統計情報を更新する自動ANALYZEが実行されていたかどうかを確認する観点もあります。</p>
<pre>log_autovacuum_min_duration = 0
# PostgreSQL 14までデフォルト-1(無効)、PostgreSQL 15からデフォルト10min(10分)
</pre>
<h3>ロック待ち</h3>
<p>突然SQLの実行が遅くなった時に、ロック待ちをしていたかどうかを確認したい場合があります。デフォルトではoff(無効)となっている<code>log_lock_waits</code>をon(有効)にしておくと、<code>deadlock_timeout</code>で設定した時間(デフォルト1秒)ロック待ちをするとPostgreSQLログに出力されます。ただし<code>deadlock_timeout</code>の設定値が少なすぎるとマニュアルにも説明があるようにチェックの負荷がかかるため、本当にデッドロックが発生した場合に検知が遅くなるデメリットはありますが、チェックの負荷が軽減されるメリットもあるのでデフォルト(1秒)よりも長い時間、例えば5秒とか10秒とかを設定しましょう。下記例では10秒を設定しています。</p>
<pre>log_lock_waits = on # デフォルト off
deadlock_timeout = 10s # デフォルト 1s
</pre>
<h3>遅延SQL</h3>
<p><code>log_min_duration_statement</code>(デフォルト:-1(無効))に設定した時間以上実行にかかったSQLはPostgreSQLログに出力されます。<br />
下記設定例では、10秒を設定していますので、10秒以上かかったSQLはPostgreSQLログに出力されます。</p>
<pre>log_min_duration_statement = 10s # デフォルト-1(無効)
</pre>
<h3>上記の設定をすべて実行した場合</h3>
<p>上記の設定をすべて実行した場合、SQLの遅延が発生するとSQL文が実行時間と共にPostgreSQLログに出力されます。例えばSQL Aが11秒かかったことが分かり、その際、一緒にロック待ちのログが出ていれば、ロック待ちの影響で遅延が発生したことがわかります。<br />
また、遅延が発生したタイミングで自動VACUUMもしくはチェックポイントが実行されていたとか、遅延が発生する直前に自動ANALYZEが実行されていたかもわかりますので、同じタイミングで実行されていた自動VACUUMやチェックポイントの可能性があるかないか、また自動ANALYZEにより実行計画を作成する元となるオプティマイザ統計情報が変更されて実行計画が変更された可能性があるかないかが判断可能となります。<br />
しかし、SQLが遅延した場合に実際に実行計画が変化したのかどうかまではわかりません。SQLが遅延した時に実行計画の情報をPostgreSQLログに出力するauto_explainモジュールについて後で紹介します。</p>
<h2>メモリ設定関連パラメータ</h2>
<h3>共有バッファ</h3>
<p>PostgreSQLマニュアルには1GB以上物理メモリがあるサーバでの<code>shared_buffers</code>の妥当な初期値は物理メモリの25%という説明が記載されていますので、共有バッファはサーバの物理メモリの4分の1に設定しましょう。例えば、物理メモリが64GBならば16GBを設定します。下記設定例は、物理メモリが64GBの場合です。</p>
<pre>shared_buffers = 16GB
</pre>
<p>サーバのメモリがどのように使用されるか、使用される比率について説明します。<br />
PostgreSQLはOSのファイルシステム上にデータベースのファイルを作成しますので、PostgreSQLの共有メモリで全体の25%、OS側でファイルのキャッシュとして全体の25%、合わせると物理メモリ全体の半分(50%)がPostgreSQLのデータベースファイルで使われ、残りの半分(50%)でOS自体が使用するメモリとOSが起動するプロセス、そしてPostgreSQLのプロセスがメモリを使用します。</p>
<h3>ワークメモリ</h3>
<p>SQLを実行する際にソートやハッシュ検索などの処理ではPostgreSQLプロセスが<code>work_mem</code>で設定された値を上限としてメモリを使用しようとしますが、設定された値では足りない場合にディスクを使って読み書きをして処理をします。メモリ上の読み書きに比べてディスク上の読み書きは桁違いに遅いので、SQLの実行に影響しますので、サーバのメモリが不足しない程度に<code>work_mem</code>に大きな値を設定します。下記の例では、16MBを設定しています。</p>
<pre>work_mem = 16MB
</pre>
<p><code>work_mem</code>で設定した値でメモリ上で処理が済んでいるかどうかを確認するには、ディスク上の処理が発生した場合にPostgreSQLログに出力する<code>log_temp_files</code>を0に設定して、SQLを実行します。ディスクを使用したら、ファイル削除時にPostgreSQLログに使用した一時ファイルが出力されます。</p>
<pre>log_temp_files = 0 # デフォルト -1(無効)
</pre>
<p>postgresql.confで上記の設定をすると、ディスク上の処理がすべてPostgreSQLログに出てしまいますので、一時的に<code>log_temp_files=0</code>に変更して<code>pg_ctl reload</code>で設定を反映、ディスク上の使用有無をチェックするSQL文を実行、<code>log_temp_files=-1</code>に戻して<code>pg_ctl reload</code>で設定を反映することで、調査期間の間だけPostgreSQLログに出力することが可能です。</p>
<p>また、特定のSQLを取り出してpsqlで実行可能な場合は、下記の例のようにセッション単位で<code>log_temp_files=0</code>に設定してSQLを実行することも可能です。psqlの画面上に表示するために<code>client_min_messages</code>も設定することで、<code>LOG: temporary file: path "base/pgsql_tmp/pgsql_tmp13483.0", size 10731520</code>が表示されて一時ファイルが使用されていることが分かります。</p>
<pre>db1=# SET log_temp_files = 0;
SET
db1=# SHOW log_temp_files;
log_temp_files
----------------
0
(1 row)

db1=# SET client_min_messages = log;
SET
db1=# EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM pgbench_accounts ORDER BY abalance DESC;
LOG: temporary file: path "base/pgsql_tmp/pgsql_tmp13483.0", size 10731520
QUERY PLAN

------------------------------------------------------------------------------------------------------------------------------
----
Sort (cost=16476.32..16726.32 rows=100000 width=97) (actual time=65.871..80.323 rows=100000.00 loops=1)
Sort Key: abalance DESC
Sort Method: external merge Disk: 10480kB
Buffers: shared hit=1704, temp read=1310 written=1313
-&gt; Seq Scan on pgbench_accounts (cost=0.00..2701.00 rows=100000 width=97) (actual time=0.022..14.887 rows=100000.00 loops
=1)
Buffers: shared hit=1701
Planning:
Buffers: shared hit=73
Planning Time: 0.571 ms
Execution Time: 89.080 ms
(10 rows)
</pre>
<p>上記のSQLで一時ファイルのDisk I/Oを使用しない<code>work_mem</code>の値を調べるには、実行計画でDiskが出なくなる<code>work_mem</code>の値を、セッション単位で一時的に必要最小限の大きな値に設定して、実行計画を表示させます。下記例では、16MBに設定した時に、<code>external merge Disk</code>が<code>quicksort Memory</code>に変わりましたので、このSQLの実行直前にセッション単位のSET文で<code>SET work_mem ='16MB';</code>に設定すればメモリ上のソートで性能向上が見込めます。</p>
<pre>db1=# SHOW work_mem;
work_mem
----------
4MB
(1 row)

db1=# SET work_mem = '16MB';
SET
db1=# EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM pgbench_accounts ORDER BY abalance DESC;
QUERY PLAN

------------------------------------------------------------------------------------------------------------------------------
----
Sort (cost=11005.82..11255.82 rows=100000 width=97) (actual time=38.479..51.692 rows=100000.00 loops=1)
Sort Key: abalance DESC
Sort Method: quicksort Memory: 14791kB
Buffers: shared hit=1701
-&gt; Seq Scan on pgbench_accounts (cost=0.00..2701.00 rows=100000 width=97) (actual time=0.020..12.699 rows=100000.00 loops
=1)
Buffers: shared hit=1701
Planning Time: 0.084 ms
Execution Time: 56.287 ms
(8 rows)

</pre>
<h2>SQL遅延時の対応について</h2>
<p>auto_explain拡張を紹介する前に、SQLが遅延した時にどのような対応を取るのかについて説明します。</p>
<h3>オプティマイザ統計情報の最新化と不要領域の整理</h3>
<p>SQLの遅延が発生した時にまず実行してほしいのが、SQLの実行計画の基となっているオプティマイザ統計情報の最新化と、SQLの検索対象テーブルの不要領域の整理です。<br />
SQLの検索対象となっているテーブル全て、特に対象テーブルのみに限定する必要が無い場合はデータベースに対して<code>VACUUM (ANALYZE)</code>を実行します。</p>
<p>下記実行例では、どのような実行状況なのかを表示するために<code>VERBOSE</code>オプションを付けています。</p>
<pre>VACUUM (ANALYZE,VERBOSE) 検索対象テーブル;
</pre>
<p>もしくは、対象データベースに接続した状態で、引数を省略するとデータベース全体に対して<code>VACUUM (ANALYZE)</code>が実行されます。</p>
<pre>VACUUM (ANALYZE,VERBOSE);
</pre>
<p>上記コマンドの実行で、現在テーブルに入っているデータで実行計画を作成するベースとなるオプティマイザ統計情報を最新化するとともに、削除済みだがまだ回収されていない不要領域が回収されます。そのため、実行計画を策定するためのオプティマイザ統計情報がテーブルの最新状況を反映していない乖離した古い状態で、効率の悪い不適切な実行計画によりSQLが遅延していた場合は、統計情報が最新化された後の再実行では遅延が解消されます。</p>
<h3>実行計画の取得</h3>
<p>上記の<code>VACUUM (ANALYZE)</code>をSQLの対象テーブルすべてに対して実行しても性能が改善しない場合、例えば普段0.5秒程度で実行されているSQLが急に10秒とかかかるようになった時に取得が必要な情報が<code>EXPLAIN (ANALYZE, BUFFERS) 遅延SQL;</code>で出力される実行計画です。</p>
<pre>EXPLAIN (ANALYZE, BUFFERS) 遅延SQL;
</pre>
<p><code>EXPLAIN</code>コマンドは引数のSQLをどのように実行するかの詳細ステップを書いた実行計画を出力するコマンドで、<code>ANALYZE</code>オプションは実際に実行した時間を出力、<code>BUFFERS</code>オプションはファイルアクセス時に共有バッファから読み込んだのかファイルから読み込んだのかの情報とアクセスしたバッファ数を出力します。</p>
<p>実行例を下記に示します。1回目はDB起動直後、2回目は連続して実行しました。2回の違いとして、<code>shared read</code>(Diskからの読み込み)が<code>shared hit</code>(共有バッファからの読み込み)になっていること、一度読み込まれて共有バッファからの読み込みになったので2回目の時間が<code>Planning</code>も<code>Execution</code>も短くなっていることが分かります。</p>
<pre>$ psql db1
psql (18.6)
Type "help" for help.

db1=# EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM pgbench_accounts;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------
Seq Scan on pgbench_accounts (cost=0.00..2640.00 rows=100000 width=97) (actual time=2.176..28.884 rows=100000.00 loops=1)
Buffers: shared read=1640
Planning:
Buffers: shared hit=44 read=11
Planning Time: 0.846 ms
Execution Time: 33.923 ms
(6 rows)

db1=# EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM pgbench_accounts;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
Seq Scan on pgbench_accounts (cost=0.00..2640.00 rows=100000 width=97) (actual time=0.069..9.271 rows=100000.00 loops=1)
Buffers: shared hit=1640
Planning Time: 0.070 ms
Execution Time: 17.789 ms
(4 rows)

db1=#
</pre>
<p>上記の実行結果を、通常の速い時に取っておき、遅延が発生した遅い時にも取れば、実行計画そのものが変わったのか実行計画は同一なのか、遅い時はどのステップでどの程度遅くなっているのかの比較ができて、通常の速い時とどう違って遅いのかの理由が分かります。</p>
<p>なお、どの実行計画のどの段階でどの列の結果を出力しているのかのオプションである<code>VERBOSE</code>オプションもあわせて指定すると、実行計画がどのように動作しているのか分かりやすくなります。下記実行例では、Outputの行が<code>VERBOSE</code>オプションによって追加された情報になります。</p>
<pre>db1=# EXPLAIN (ANALYZE, BUFFERS, VERBOSE) SELECT * FROM pgbench_accounts;
QUERY PLAN

------------------------------------------------------------------------------------------------------------------------------
-----
Seq Scan on public.pgbench_accounts (cost=0.00..2640.00 rows=100000 width=97) (actual time=0.018..11.627 rows=100000.00 loop
s=1)
Output: aid, bid, abalance, filler
Buffers: shared hit=1640
Planning:
Buffers: shared hit=60
Planning Time: 1.456 ms
Execution Time: 19.776 ms
(7 rows)

db1=#
</pre>
<h2>拡張(EXTENSION)</h2>
<p>PostgreSQLの⼤きな特徴として、様々な機能を追加できる拡張(EXTENSION)があります。<br />
ここでは性能問題の調査や分析に役立つ代表的な拡張を紹介します。</p>
<p>拡張を使用するためには、拡張が入ったパッケージをPostgreSQLインストール時にあらかじめインストールしておき、<code>CREATE EXTENSION</code>や<code>shared_preload_libraries</code>変更などの設定が必要ですが、そもそも拡張をインストールしていない場合は、拡張を使用する設定をする前に後から追加でインストールが必要となります。<br />
しかし、既にシステムが本番稼働をした状況で、後から拡張をインストールをするのはハードルが高いので、後から使いたい時に使えるよう、PostgreSQLインストール時に一緒にインストールしておくのが望ましいです。</p>
<h3>拡張の種類</h3>
<p>PostgreSQL本体と一緒に同梱されているcontribモジュールと、contribモジュール以外(サードパーティー)モジュールがあります。</p>
<p>contribモジュールは、PostgreSQL本体と合わせてリリースされていますので、PostgreSQLサーバのインストール時に一緒にインストールしておきましょう。contribモジュールをインストールしておけば、後からcontribモジュールに含まれる拡張を導入することが可能です。contribモジュール以外の拡張は、サードパーティーモジュールと呼ばれ、現在はGitHubサイトで公開されていることが多いです。ダウンロード可能な形式はソースコードがメインで、リリース時点で公開されているRHELのrpmファイルもダウンロード可能な場合もあります。</p>
<h3>auto_explain(contrib)</h3>
<p>設定した時間以上かかったSQLの実行計画をPostgreSQLログに出力します。今回のこの記事で一番紹介したかったのはこのauto_explain拡張モジュールです。</p>
<p>サポートでよく「SQLが突然遅くなった。」という問い合わせをいただきますが、遅くなった時の情報が何もないため、なぜ遅くなったのかの原因調査が出来ません。問い合わせをいただいた時の返答はいつも「次回遅延が発生している間に<code>EXPLAIN (ANALYZE, BUFFERS) SQL;</code>を実行して実行計画を取得してください。」とお伝えしています。遅延している状況がずっと続いている、または定期的に遅延していてその時間帯が日中の場合は<code>EXPLAIN (ANALYZE, BUFFERS) SQL;</code>を手動で実行して実行結果を取得することは可能ですが、遅延の発生が真夜中であるとか、遅延がいつ発生するのかが不明な場合は人手で<code>EXPLAIN (ANALYZE, BUFFERS) SQL;</code>を実行することは困難です。そのような状況で一番役立つのがこのauto_explainです。</p>
<p>auto_explainの使い方について説明します。postgresql.confに下記の設定を追加して、PostgreSQLを再起動するとauto_explainが有効になって、例えば10秒以上かかるSQLが実行されると自動的にSQL実行時の<code>EXPLAIN(ANALYZE, BUFFERS) SQL;</code>の情報がPostgreSQLログに出力されます。</p>
<p>PostgreSQLログにauto_explainでの実行結果が出力されれば、性能悪化時と通常時の実行計画を比較できます。<br />
下記はauto_explainを使うために必要な設定で、設定追加後PostgreSQLを再起動すると10秒以上かかったSQL文の実行計画がPostgreSQLログに出力されます。</p>
<pre>shared_preload_libraries = 'auto_explain'
auto_explain.log_min_duration = 10s
auto_explain.log_analyze = on
auto_explain.log_buffers = on
auto_explain.log_timing = off # ノードごとの実行時間を取得する場合はon
</pre>
<p>なお、auto_explainの有効化はデータベースサーバ全体だけではなく、セッション単位でも下記の方法で有効化可能です。下記の例は、psqlで接続した後、</p>
<pre>db1=# LOAD 'auto_explain';
LOAD
db1=# set auto_explain.log_min_duration = 0;
SET
db1=# set auto_explain.log_analyze = on;
SET
db1=# SELECT COUNT(*) FROM pgbench_accounts;
count
--------
100000
(1 row)

db1=#
</pre>
<p>上記の実行でのPostgreSQLログ出力は下記となります。</p>
<pre>2026-10-06 20:51:58.504 JST [5786] LOG: duration: 7.090 ms plan:
Query Text: SELECT COUNT(*) FROM pgbench_accounts;
Aggregate (cost=2924.00..2924.01 rows=1 width=8) (actual time=7.080..7.081 rows=1.00 loops=1)
-&gt; Seq Scan on pgbench_accounts (cost=0.00..2674.00 rows=100000 width=0) (actual time=0.008..4.326 rows=100000.00 loops=1)
</pre>
<p>※注：<code>auto_explain.log_analyze</code>を有効(on)にすると、すべてのSQL実行で性能上のマイナスが起こり得ます。<code>auto_explain.log_timing = off</code>を追加することでノードごとの時間情報が無効になりますが行数の情報は取得でき性能面での損失が改善されますので、本番環境では通常は<code>auto_explain.log_timing</code>を無効にしておくことが望ましいと考えられます。<br />
実際に遅延が発生し、ノードごとの時間情報も必要と判断した場合のみ、性能面でのデメリットを認識した上で<code>auto_explain.log_timing = on</code>に変更し、情報が取得でき次第速やかに無効(<code>off</code>)にする運用がよいでしょう。</p>
<p>auto_explain(F.3. auto_explain — 低速な問い合わせ実行計画のログ)<br />
<a href="https://www.sraoss.co.jp/PostgreSQL/Manual/document/18/auto-explain.html" target="_blank" rel="noopener">https://www.sraoss.co.jp/PostgreSQL/Manual/document/18/auto-explain.html</a></p>
<h4>pg_hint_plan(サードパーティー)</h4>
<p>なお、今回紹介している性能調査とは異なりますが、auto_explainでPostgreSQLログに出力した実行計画が性能悪化の原因であった場合、SQLに埋め込んだヒント句で強制的に実行計画を変更するpg_hint_planという拡張も紹介しておきます。<br />
オプティマイザ統計情報を最新化しても実行計画の形が変わってしまってSQLの性能が悪化した場合、例えば結合方式が速い時は<code>Hash Join</code>なのに遅延時は<code>Nested Loop</code>に変更されてしまったとか、複数テーブルある結合で速い時と遅い時でテーブルを結合する順番が異なるなどの場合に、強制的に速い時の実行計画にあわせるためにSQLにヒント句を埋め込んでヒント句の通りに実行計画を強制変更します。SQLにヒント句を埋め込まない指定も利用可能です。弊社ブログに解説がありますのでご参照ください。</p>
<p>pg_hint_plan (PostgreSQL 実行計画制御ツール)<br />
<a href="https://www.sraoss.co.jp/tech-blog/pgsql/pg_hint_plan" target="_blank" rel="noopener">https://www.sraoss.co.jp/tech-blog/pgsql/pg_hint_plan/</a></p>
<h3>pg_stat_statements(contrib)</h3>
<p>pg_stat_statementsは日常的にどのSQLが時間を使っているのかを把握するための拡張です。短い実行時間のSQLでも実行回数が多ければ合計実行時間は大きくなります。pg_stat_statementsはサーバで実行されたすべてのSQLのプラン生成時と実行時の統計情報を記録します。このモジュールはPostgreSQLログへの出力は無く、情報の参照は情報を取得しているデータベースに接続してpg_stat_statementsビューをSELECTする方法となります。</p>
<p>下記はpg_stat_statementsを使うために必要な設定で、設定追加後PostgreSQLを再起動した後、利用するデータベースに接続して<code>CREATE EXTENSION pg_stat_statements;</code>を実行すると、pg_stat_statementsビューが参照可能となります。</p>
<pre>shared_preload_libraries = 'pg_stat_statements'
</pre>
<p>DB再起動後、下記を実行。</p>
<pre>db1=# CREATE EXTENSION pg_stat_statements;
CREATE EXTENSION
db1=# \dx
List of installed extensions
Name | Version | Default version | Schema | Description

--------------------+---------+-----------------+------------+----------------------------------------------------------------
--------
pg_stat_statements | 1.12 | 1.12 | public | track planning and execution statistics of all SQL statements e
xecuted
plpgsql | 1.0 | 1.0 | pg_catalog | PL/pgSQL procedural language
(2 rows)

db1=#
</pre>
<p>PostgreSQLマニュアルの実行例を実行した例。合計実行時間(<code>total_exec_time</code>)の多い順から、SQL、呼び出し回数、合計実行時間、行数、算出したヒット率が表示されています。</p>
<pre>db1=# \x
Expanded display is on.
db1=# 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 | 17180.62709200005
rows | 3000
hit_percent | 99.9982951155059245
-[ RECORD 2 ]---+-----------------------------------------------------------------------------------------------------
query | UPDATE pgbench_tellers SET tbalance = tbalance + $1 WHERE tid = $2
calls | 3000
total_exec_time | 14783.155679999958
rows | 3000
hit_percent | 99.9957805907172996
-[ RECORD 3 ]---+-----------------------------------------------------------------------------------------------------
query | UPDATE pgbench_accounts SET abalance = abalance + $1 WHERE aid = $2
calls | 3000
total_exec_time | 111.09148400000022
rows | 3000
hit_percent | 92.5940860215053763
-[ RECORD 4 ]---+-----------------------------------------------------------------------------------------------------
query | SELECT abalance FROM pgbench_accounts WHERE aid = $1
calls | 3000
total_exec_time | 28.88099300000003
rows | 3000
hit_percent | 100.0000000000000000
-[ RECORD 5 ]---+-----------------------------------------------------------------------------------------------------
query | INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP)
calls | 3000
total_exec_time | 21.792536000000027
rows | 3000
hit_percent | 100.0000000000000000

db1=#
</pre>
<p>pg_stat_statements(F.32. pg_stat_statements — SQL文のプラン生成時と実行時の統計情報を記録する)<br />
<a href="https://www.sraoss.co.jp/PostgreSQL/Manual/document/18/pgstatstatements.html" target="_blank" rel="noopener">https://www.sraoss.co.jp/PostgreSQL/Manual/document/18/pgstatstatements.html</a></p>
<h3>性能問題の調査に役立つ情報の継続収集</h3>
<p>性能問題の調査では、問題発生時の状況だけでなく、過去との比較も重要です。<br />
そのためには PostgreSQL の統計情報や OS のリソース情報を継続的に収集しておくことが有効です。</p>
<p>収集方法としては、</p>
<ul>
<li>sysstat(sar)</li>
<li>各種監視ツール</li>
<li>PostgreSQL向け収集ツール</li>
</ul>
<p>などがあります。<br />
その中でも PostgreSQL の統計情報と OS リソース情報をまとめて時系列で取得できるツールとしてpg_statsinfo があります。</p>
<h4>pg_statsinfo(サードパーティー)</h4>
<p>pg_statsinfo は PostgreSQL の統計情報やOSリソース情報を時系列で取得・保存するツールです。PostgreSQL 統計情報やOSリソース情報を時系列で取得するスナップショットを保存するためのリポジトリDBが必要となりますが、pg_statsinfoを使った運用をしていると何か性能問題が発生した時に過去の情報を参照できるのでとても便利です。上で紹介したpg_stat_statementsと、ヒント句を取得できるpg_store_plansと併用することでクエリの統計情報と実行計画も一緒に保存できるため、どのSQLが性能に影響していた可能性があるのか、また実行計画の変化によって性能が悪化した時も後から確認できます。<br />
弊社ブログに解説がありますのでご参照ください。</p>
<p>pg_statsinfo(統計・OSリソース情報時系列取得保存ツール)<br />
<a href="https://www.sraoss.co.jp/tech-blog/pgsql/pg_statsinfo/" target="_blank" rel="noopener">https://www.sraoss.co.jp/tech-blog/pgsql/pg_statsinfo/</a></p>
<p>pg_statsinfoは現在も有用なツールですが、監視製品やクラウド環境の監視機能などを利用して継続収集を行う方法も合わせてご検討ください。</p>
<h2>まとめ</h2>
<p>PostgreSQLの性能問題は、「遅くなった」という事実だけでは原因を特定できません。重要なのは、問題発生時に何が起きていたのかを後から分析できる情報を残しておくことです。</p>
<p>本記事では、チェックポイント、自動VACUUM、自動ANALYZE、ロック待ち、遅延SQLを記録するためのログ設定と、遅延時の実行計画を自動取得できるauto_explain、調査結果を踏まえて実行計画を制御できるpg_hint_plan、SQL実行統計を蓄積するpg_stat_statements、PostgreSQLの統計情報やOSリソース情報を時系列で取得・保存するpg_statsinfoを紹介しました。</p>
<p>特に、SQL性能問題の原因究明においては、遅延発生時のEXPLAIN (ANALYZE, BUFFERS)の取得が最も重要です。auto_explainを導入しておけば、人が操作できない時間帯に発生した遅延であっても、実行計画を記録できます。遅延時の実行計画が通常時の実行計画と異なる場合は、pg_hint_planにより強制的に通常時の実行計画に合わせることが可能となります。ただし、実行計画変更の根本原因が統計情報やデータ分布の変化にある場合もあり、pg_hint_planは原因調査や恒久対策を行った上で必要に応じて利用します。</p>
<p>PostgreSQLの性能問題への対応は、問題発生後に慌てて調査方法を考えるのではなく、平常時から必要な情報を取得できる状態を作っておくことが重要です。本記事がそのための設定見直しの参考になれば幸いです。</p>
<p>&nbsp;</p>
<div>
<a href="https://www.sraoss.co.jp/prod_serv/support/pgsql-mainte/?utm_source=sraoss-techblog&utm_medium=referral&utm_campaign=under-article-pgsql" rel="noopener" style="color:initial;" target="_blank" class="no-ext"></p>
<div style="background-color:#DAEFF6; padding:20px; border-radius:8px; text-align:center;">
SRA OSSでは、PostgreSQL/Pgpool-II のサポートを提供しております。<br />
詳しくは<strong style="color:#2581c4;">弊社ホームページ</strong>をご覧ください。
</div>
<p></a>
</div>

]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>pg_hint_plan (PostgreSQL 実行計画制御ツール)</title>
		<link>https://www.sraoss.co.jp/tech-blog/pgsql/pg_hint_plan/</link>
		
		<dc:creator><![CDATA[データベース技術グループ]]></dc:creator>
		<pubDate>Fri, 26 Oct 2018 01:01:46 +0000</pubDate>
				<category><![CDATA[PostgreSQL]]></category>
		<category><![CDATA[pg_hint_plan]]></category>
		<category><![CDATA[サードパーティーソフトウェア]]></category>
		<guid isPermaLink="false">https://www.sraoss.co.jp/tech-blog/?p=3144</guid>

					<description><![CDATA[更新日：2026年6月1日 1. pg_hint_planとは pg_hint_plan は PostgreSQL のクエリの実行計画を制御するためのモジュールです。本モジュールにより、他のリレーショナルデータベースで一 ...]]></description>
										<content:encoded><![CDATA[<p>更新日：2026年6月1日</p>
<h3>1. pg_hint_planとは</h3>
<p>pg_hint_plan は PostgreSQL のクエリの実行計画を制御するためのモジュールです。本モジュールにより、他のリレーショナルデータベースで一般的な機能であるヒント句を PostgreSQL で利用することが可能になります。ヒント句とは実行されるクエリの実行計画の決定をプランナに任せきりにせず、プランナに実行計画を直接指示することを可能とする機能です。</p>
<p><span id="more-3144"></span></p>
<p>PostgreSQL ではクエリの実行計画はプランナが統計情報を用いて決定します。プランナのバグや使用する統計情報が正確でないことで不適切な実行計画が作成されたり、<br />
刻々と変化する統計情報に依存する実行計画によってクエリの実行性能が不安定になる場合があります。pg_hint_plan を用いることで、プランナの不適切な実行計画を改善したり、統計情報に依存する不安定な実行計画を安定化することが可能になります。</p>
<p>2026年 6月時点では、PostgreSQL 18.x に対応した 1.8.0 がリリースされています。そのほかも PostgreSQL バージョン 9.1 以降のそれぞれの PostgreSQL バージョンに対応するpg_hint_plan バージョンがリリースされています。</p>
<p>Windows むけのバイナリ配布物は公式サイトからリリースされていませんが、Windowsむけのビルドは可能です。<a href="https://www.sraoss.co.jp/prod_serv/support/pgsql-mainte/?utm_source=sraoss-techblog&amp;utm_medium=referral&amp;utm_campaign=under-article-pgsql" target="_blank" rel="noopener">SRA OSS の PostgreSQLサポートサービス</a>では pg_hint_plan の Windowsバイナリ提供を行っています。</p>
<p>PostgreSQL 9.0.x 以前のバージョンに対応していないのは PostgreSQL 側にヒント句を追加できる枠組みが備わっていないためです。</p>
<h3>2. 本文書の環境</h3>
<p>本文書は、PostgreSQL 18.3 と pg_hint_plan 1.8.0 を使って、pg_hint_plan の Linux 上での使い方を解説します。</p>
<h3>3. 導入方法</h3>
<p>本節では Rocky Linux 10.1で pg_hint_plan 1.8.0 を導入する例を示します。PostgreSQL 18 が RPM でインストールされているものとします。</p>
<h4>◆ インストール</h4>
<p>pg_hint_plan は <a href="https://yum.postgresql.org/" target="_blank" rel="noopener noreferrer">PGDGリポジトリ</a>からインストールすることが可能です。以下のように dnf コマンドを実行してインストールします。</p>
<pre># dnf install pg_hint_plan_18</pre>
<h4>◆ セッションへのロード</h4>
<p>セッションにおいて pg_hint_plan を利用するには予めロードが必要になります。特定のセッションでのみ利用する場合は LOAD コマンドを実行してください。</p>
<pre>db1=# LOAD ‘pg_hint_plan’;
LOAD</pre>
<p>全セッションで利用する場合は PostgreSQL の 設定ファイル postgresql.conf の shared_preload_libraries パラメータに &#8216;pg_hint_plan&#8217; を追加してください。</p>
<pre>shared_preload_libraries = 'pg_hint_plan'</pre>
<h4>◆ データベースへの登録</h4>
<p>pg_hint_plan では、クエリにコメントでヒントを指定する方法に加え、hint テーブルにヒントを登録して利用することもできます。hint テーブルを利用する場合は、対象データベースに拡張機能を登録し、</p>
<pre>$ psql -d db1 -c "CREATE EXTENSION pg_hint_plan"</pre>
<p>postgresql.conf の pg_hint_plan.enable_hint_table を on に設定してください。</p>
<pre>pg_hint_plan.enable_hint_table = on</pre>
<p>なお、pg_hint_plan 1.7.0 以降では Query Identifier (query_id) を利用して対象クエリを識別するため、以下設定も必要です。</p>
<pre>compute_query_id = on</pre>
<p>データベースの登録が済むと次のオブジェクトが作成されます。</p>
<ul>
<li>hint_plan スキーマ</li>
<li>hint_plan.hints テーブル</li>
<li>hint_plan.hints_pkey インデックス</li>
<li>hint_plan.hints_id_and_app インデックス（1.7.0より前は hint_plan.hints_norm_and_app）</li>
<li>hint_plan.hints_id_seq シーケンス</li>
</ul>
<h3>4. ヒントの使用</h3>
<p>ヒントはクエリにコメントの形で与えます。ヒント用のコメントは通常のコメントと区別するために /*+ で始めるようにして、「/*+ ヒント */ クエリ」という書式で記述します。複数のヒントを指定する場合には空白文字で区切って並べます。</p>
<p>以下表に示すカテゴリのヒントが利用可能です。使用できるヒント一覧はマニュアルに記載があります。</p>
<p style="margin-bottom: 0px;">&lt;利用可能なヒントのカテゴリ&gt;</p>
<table style="margin-top: 0px; margin-bottom: 20px;" border="1" cellspacing="0">
<tbody>
<tr>
<td>スキャン方式</td>
<td>テーブルのスキャン方式を指定できます</td>
</tr>
<tr>
<td>結合順序</td>
<td>複数あるテーブルをどの順で結合するか指定できます</td>
</tr>
<tr>
<td>結合方式</td>
<td>テーブル結合の方式を指定できます</td>
</tr>
<tr>
<td>見積件数補正</td>
<td>テーブル結合結果の件数見積もりを補正する指定ができます</td>
</tr>
<tr>
<td>パラレル実行</td>
<td>パラレル実行を強制あるいは禁止する指定ができます</td>
</tr>
<tr>
<td>設定パラメータ</td>
<td>SETコマンドで指定できるパラメータ設定をヒントから与えます</td>
</tr>
<tr>
<td>インデックス無効化</td>
<td>指定したインデックスをクエリ計画時に無効化します</td>
</tr>
<tr>
<td>結合動作制御</td>
<td>結合時に内部表の結果をメモ化するか制御できます</td>
</tr>
</tbody>
</table>
<p>ヒント句一覧<br />
<a href="https://github.com/ossc-db/pg_hint_plan/blob/master/docs/hint_list.md" target="_blank" rel="noopener noreferrer">https://github.com/ossc-db/pg_hint_plan/blob/master/docs/hint_list.md</a></p>
<h4>◆ 実行例</h4>
<p>ここから実際にヒントを与えて実行計画を変更する例を示します。この例ではヒントを指定することでプランナの計画した不適切な実行計画よりも適切な実行計画を指定しています。</p>
<p>本検証例のサンプルデータは以下の通り <a href="https://www.sraoss.co.jp/PostgreSQL/Manual/document/18/pgbench.html" target="_blank" rel="noopener noreferrer">pgbench</a> コマンドで作成しています。</p>
<pre>$ pgbench -U postgres -i -s 10 db1
creating tables...
...
$ psql db1
db1=# VACUUM ANALYZE;
VACUUM</pre>
<p>pgnemch コマンドで作成された pgbench_accounts テーブル、pgbench_branches テーブルに対して結合とソート操作を行うクエリの実行計画を検証します。</p>
<p style="margin-bottom: 0px;">&lt;ヒントを指定しない場合&gt;</p>
<pre>db1=# EXPLAIN ANALYZE
        SELECT * FROM pgbench_branches b
          JOIN pgbench_accounts a ON b.bid = a.bid ORDER BY a.aid;
                                                                              QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Nested Loop  (cost=0.57..66151.81 rows=1000000 width=461) (actual time=0.093..340.277 rows=1000000.00 loops=1)
   Buffers: shared hit=20 read=19129
   -&gt;  Index Scan using pgbench_accounts_pkey on pgbench_accounts a  (cost=0.42..42377.43 rows=1000000 width=97) (actual time=0.083..128.160 rows=1000000.00 loops=1)
         Index Searches: 1
         Buffers: shared read=19129
   -&gt;  Memoize  (cost=0.15..0.16 rows=1 width=364) (actual time=0.000..0.000 rows=1.00 loops=1000000)
         Cache Key: a.bid
         Cache Mode: logical
         Hits: 999990  Misses: 10  Evictions: 0  Overflows: 0  Memory Usage: 2kB
         Buffers: shared hit=20
         -&gt;  Index Scan using pgbench_branches_pkey on pgbench_branches b  (cost=0.14..0.15 rows=1 width=364) (actual time=0.007..0.007 rows=1.00 loops=10)
               Index Cond: (bid = a.bid)
               Index Searches: 10
               Buffers: shared hit=20
 Planning:
   Buffers: shared hit=4
 Planning Time: 0.145 ms
 Execution Time: 363.231 ms
(18 rows)</pre>
<p>ヒントを指定しない場合は Index Scan, Nested Loop, Memoize を利用した実行計画が計画されました。<br />
これに対して Seq Scan, Hash Join を計画するようにヒント指定してみます。SeqScan(a) はテーブル a をシーケンシャルスキャンすること、HashJoin(a b) はテーブル a , b をハッシュジョインすることを意味します。ヒント指定においては、クエリ内でテーブル名に別名が付けられている場合、ヒント指定では別名を使う点に注意してください。</p>
<p style="margin-bottom: 0px;">&lt;ヒントを指定した場合&gt;</p>
<pre>db1=# /*+ HashJoin(a b) SeqScan(a) */
      EXPLAIN ANALYZE
        SELECT * FROM pgbench_branches b
          JOIN pgbench_accounts a ON b.bid = a.bid ORDER BY a.aid;
                                                                  QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------
 Sort  (cost=546787.57..549287.57 rows=1000000 width=461) (actual time=937.656..1018.152 rows=1000000.00 loops=1)
   Sort Key: a.aid
   Sort Method: external merge  Disk: 112576kB
   Buffers: shared hit=13754 read=2641, temp read=28139 written=28176
   -&gt;  Hash Join  (cost=1.23..30132.72 rows=1000000 width=461) (actual time=2.499..528.706 rows=1000000.00 loops=1)
         Hash Cond: (a.bid = b.bid)
         Buffers: shared hit=13754 read=2641
         -&gt;  Seq Scan on pgbench_accounts a  (cost=0.00..26394.00 rows=1000000 width=97) (actual time=2.192..232.446 rows=1000000.00 loops=1)
               Buffers: shared hit=13753 read=2641
         -&gt;  Hash  (cost=1.10..1.10 rows=10 width=364) (actual time=0.139..0.183 rows=10.00 loops=1)
               Buckets: 1024  Batches: 1  Memory Usage: 9kB
               Buffers: shared hit=1
               -&gt;  Seq Scan on pgbench_branches b  (cost=0.00..1.10 rows=10 width=364) (actual time=0.061..0.062 rows=10.00 loops=1)
                     Buffers: shared hit=1
 Planning Time: 2.741 ms
 Execution Time: 1071.856 ms
(16 rows)
</pre>
<p>ヒントにより、Hash Join および Seq Scan を利用する実行計画へ変更されていることが確認できます。</p>
<h4>◆ ヒントを予め登録する</h4>
<p>ヒントの与え方はクエリにコメントとして直接指定する方式の他に、ヒント用のテーブル hint_plan.hints に実行計画を制御したいクエリとヒントを登録しておく方式があります。</p>
<p>先ほどのクエリにテーブルでヒントを指定してみます。</p>
<p>まず、対象クエリの クエリIDを確認します。compute_query_id = on であれば、EXPLAIN VERBOSE を実行すると Query Identifier からクエリIDを確認できます。</p>
<pre>db1=# EXPLAIN (VERBOSE)
        SELECT * FROM pgbench_branches b
          JOIN pgbench_accounts a ON b.bid = a.bid ORDER BY a.aid;
                                                       QUERY PLAN
------------------------------------------------------------------------------------------------------------------------
 Nested Loop  (cost=0.57..66151.81 rows=1000000 width=461)
   Output: b.bid, b.bbalance, b.filler, a.aid, a.bid, a.abalance, a.filler
   Inner Unique: true
   -&gt;  Index Scan using pgbench_accounts_pkey on public.pgbench_accounts a  (cost=0.42..42377.43 rows=1000000 width=97)
         Output: a.aid, a.bid, a.abalance, a.filler
   -&gt;  Memoize  (cost=0.15..0.16 rows=1 width=364)
         Output: b.bid, b.bbalance, b.filler
         Cache Key: a.bid
         Cache Mode: logical
         -&gt;  Index Scan using pgbench_branches_pkey on public.pgbench_branches b  (cost=0.14..0.15 rows=1 width=364)
               Output: b.bid, b.bbalance, b.filler
               Index Cond: (b.bid = a.bid)
 <strong>Query Identifier: 3522461857817933824</strong>
(13 rows)</pre>
<p>次に hint_plan.hints テーブルに指示対象のクエリID、アプリケーション名、ヒントを登録します。このとき、アプリケーション名に ’’（空文字列）を登録することで、どの名称のアプリケーションにも適用されます。</p>
<pre>db1=# INSERT INTO hint_plan.hints (query_id, application_name, hints)
          VALUES ('3522461857817933824','','HashJoin(a b) SeqScan(a)');
INSERT 0 1</pre>
<p>以下の通り、コメントでヒントを指定せずともヒントが適用されていることが確認できます。</p>
<pre>db1=# EXPLAIN ANALYZE
        SELECT * FROM pgbench_branches b
          JOIN pgbench_accounts a ON b.bid = a.bid ORDER BY a.aid;
                                                                 QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------
 Sort  (cost=546787.57..549287.57 rows=1000000 width=461) (actual time=475.282..549.065 rows=1000000.00 loops=1)
   Sort Key: a.aid
   Sort Method: external merge  Disk: 112576kB
   Buffers: shared hit=13702 read=2693, temp read=28139 written=28176
   -&gt;  Hash Join  (cost=1.23..30132.72 rows=1000000 width=461) (actual time=0.457..166.269 rows=1000000.00 loops=1)
         Hash Cond: (a.bid = b.bid)
         Buffers: shared hit=13702 read=2693
         -&gt;  Seq Scan on pgbench_accounts a  (cost=0.00..26394.00 rows=1000000 width=97) (actual time=0.427..43.786 rows=1000000.00 loops=1)
               Buffers: shared hit=13701 read=2693
         -&gt;  Hash  (cost=1.10..1.10 rows=10 width=364) (actual time=0.018..0.020 rows=10.00 loops=1)
               Buckets: 1024  Batches: 1  Memory Usage: 9kB
               Buffers: shared hit=1
               -&gt;  Seq Scan on pgbench_branches b  (cost=0.00..1.10 rows=10 width=364) (actual time=0.011..0.012 rows=10.00 loops=1)
                     Buffers: shared hit=1
 Planning:
   Buffers: shared hit=1
 Planning Time: 0.278 ms
 Execution Time: 584.014 ms
(18 rows)</pre>
<p>テーブルでヒントを指定するときは以下の点に注意してください。</p>
<ul>
<li>「コメントでの指定」と「テーブルでの指定」の両方で指定した場合、「テーブルでの指定」が優先される</li>
<li>query_id は定数値を正規化して生成されるため、定数値ごとに異なるヒントを設定することはできない。例えば、SELECT * FROM emp WHERE emp_id = 100; と SELECT * FROM emp WHERE emp_id = 200; は同一のクエリIDになる</li>
</ul>
<h4>◆ Parallelヒント</h4>
<p>もう一つ例を示します。pg_hint_plan 1.2.0 から PostgreSQL 9.6 新機能のパラレルクエリに対応しています。Parallel (テーブル名 ワーカ数 [強制度]) というヒントを指定することでクエリをパラレルクエリとして実行させることが可能です。</p>
<p>引き続き、pgbench で生成したテーブルを使って例を示します。</p>
<p style="margin-bottom: 0px;">&lt;ヒントを指定しない場合&gt;</p>
<pre>db1=# EXPLAIN ANALYZE
        SELECT bid FROM pgbench_accounts
          UNION ALL SELECT bid FROM pgbench_branches;
                                                             QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------
 Append  (cost=0.00..31395.15 rows=1000010 width=4) (actual time=0.307..128.488 rows=1000010.00 loops=1)
   Buffers: shared hit=13588 read=2807
   -&gt;  Seq Scan on pgbench_accounts  (cost=0.00..26394.00 rows=1000000 width=4) (actual time=0.306..82.152 rows=1000000.00 loops=1)
         Buffers: shared hit=13587 read=2807
   -&gt;  Seq Scan on pgbench_branches  (cost=0.00..1.10 rows=10 width=4) (actual time=0.022..0.023 rows=10.00 loops=1)
         Buffers: shared hit=1
 Planning Time: 0.147 ms
 Execution Time: 152.405 ms
(8 rows)</pre>
<p>以下の Parallel ヒントは、pgbench_accounts テーブルをワーカ数8で実行させるように指定していますが、実際はワーカ数3で実行されています。これは Parallel ヒントの強制度はデフォルトで soft 指定であり、max_parallel_workers_per_gather のみを変更してあとはプランナに任せるので、必ずしもワーカ数は指定した値で実行されるとは限りません。</p>
<p style="margin-bottom: 0px;">&lt;強制度 soft で Parallel ヒント指定する場合&gt;</p>
<pre>db1=# /*+ Parallel(pgbench_accounts 8) */
      EXPLAIN ANALYZE
        SELECT bid FROM pgbench_accounts
          UNION ALL SELECT bid FROM pgbench_branches;
                                                                 QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------
 Gather  (cost=1000.00..103085.41 rows=1000010 width=4) (actual time=1.230..71.277 rows=1000010.00 loops=1)
   Workers Planned: 3
   Workers Launched: 3
   Buffers: shared hit=13682 read=2713
   -&gt;  Parallel Append  (cost=0.00..2084.41 rows=416671 width=4) (actual time=0.175..44.610 rows=250002.50 loops=4)
         Buffers: shared hit=13682 read=2713
         -&gt;  Parallel Seq Scan on pgbench_branches  (cost=0.00..1.06 rows=6 width=4) (actual time=0.024..0.026 rows=10.00 loops=1)
               Buffers: shared hit=1
         -&gt;  Parallel Seq Scan on pgbench_accounts  (cost=0.00..0.00 rows=322581 width=4) (actual time=0.175..29.598 rows=250000.00 loops=4)
               Buffers: shared hit=13681 read=2713
 Planning Time: 0.157 ms
 Execution Time: 106.721 ms
(12 rows)</pre>
<p>ワーカ数を強制するには強制度を hard に指定します。以下では、実際に指定したワーカ数 8 で実行されていることがわかります。</p>
<p style="margin-bottom: 0px;">&lt;強制度 hard で Parallel ヒント指定する場合&gt;</p>
<pre>db1=# /*+ Parallel(pgbench_accounts 8 hard) */
      EXPLAIN ANALYZE
        SELECT bid FROM pgbench_accounts
          UNION ALL SELECT bid FROM pgbench_branches;
                                                                  QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------
 Gather  (cost=1000.00..103085.41 rows=1000010 width=4) (actual time=1.201..179.885 rows=1000010.00 loops=1)
   Workers Planned: 8
   Workers Launched: 8
   Buffers: shared read=16395
   -&gt;  Parallel Append  (cost=0.00..2084.41 rows=416671 width=4) (actual time=1.456..142.900 rows=111112.22 loops=9)
         Buffers: shared read=16395
         -&gt;  Parallel Seq Scan on pgbench_branches  (cost=0.00..1.06 rows=6 width=4) (actual time=0.472..0.473 rows=5.00 loops=2)
               Buffers: shared read=1
         -&gt;  Parallel Seq Scan on pgbench_accounts  (cost=0.00..0.00 rows=125000 width=4) (actual time=1.454..135.136 rows=111111.11 loops=9)
               Buffers: shared read=16394
 Planning:
   Buffers: shared hit=53 read=16
 Planning Time: 4.714 ms
 Execution Time: 215.078 ms
(14 rows)</pre>
<p>なお、parallel_setup_cost や min_parallel_table_scan_size などの並列実行に関するパラメータ設定の影響により、単一リレーションでは期待通りに並列化が有効にならない場合があります。例えば以下のケースでは、hard を指定していてもシーケンシャルスキャンが選択されています。</p>
<pre>db1=# /*+ Parallel(pgbench_accounts 8 hard) */
      EXPLAIN ANALYZE  SELECT * FROM pgbench_accounts;
                                                          QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------
 Seq Scan on pgbench_accounts  (cost=0.00..26394.00 rows=1000000 width=97) (actual time=0.173..42.259 rows=1000000.00 loops=1)
   Buffers: shared hit=7533 read=8861
 Planning Time: 0.083 ms
 Execution Time: 65.553 ms
(4 rows)</pre>
<p>本ケースでは、parallel_tuple_cost をデフォルトの 0.1 から 0 にすると、並列実行が選択されるようになります。</p>
<pre>db1=# /*+ Set(parallel_tuple_cost 0) Parallel(pgbench_accounts 8 hard) */
      EXPLAIN ANALYZE SELECT * FROM pgbench_accounts;
                                                               QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------
 Gather  (cost=1000.00..1000.00 rows=1000000 width=97) (actual time=1.397..89.476 rows=1000000.00 loops=1)
   Workers Planned: 8
   Workers Launched: 8
   Buffers: shared hit=558 read=15836
   -&gt;  Parallel Seq Scan on pgbench_accounts  (cost=0.00..0.00 rows=125000 width=97) (actual time=0.192..11.408 rows=111111.11 loops=9)
         Buffers: shared hit=558 read=15836
 Planning Time: 0.102 ms
 Execution Time: 120.893 ms
(8 rows)</pre>
<h4>◆ ログメッセージとデバッグ出力</h4>
<p>クエリ実行時にヒントを解釈できなかった場合、クエリ自体はヒントが与えられなかったものとして通常通り実行されます。ヒントが適正でなかったことはログに出力されます。</p>
<p>以下に、存在しないヒントを入力したときの例を示します。</p>
<pre>db1=# /*+ error_hint */
      EXPLAIN ANALYZE
        SELECT * FROM pgbench_branches b
          JOIN pgbench_accounts a ON b.bid = a.bid ORDER BY a.aid;
INFO:  pg_hint_plan: hint syntax error at or near " error_hint "
DETAIL:  Unrecognized hint keyword "error_hint".</pre>
<p>このときのメッセージレベルは pg_hint_plan.parse_messages（デフォルト info）パラメータで指定できます。</p>
<p>また、pg_hint_plan.debug_print = on （デフォルト off）とすることで指定したヒントの実行計画への影響をログに出力させることが可能です。指定したヒントが以下 4 つのカテゴリに分類されて報告されます。</p>
<table style="margin-top: 10px; margin-bottom: 20px;" border="1" cellspacing="0">
<tbody>
<tr>
<td>used hint</td>
<td>利用されたヒント</td>
</tr>
<tr>
<td>not used hint</td>
<td>利用されなかったヒント</td>
</tr>
<tr>
<td>duplication hint</td>
<td>矛盾するヒントが重複して利用されなかったヒント</td>
</tr>
<tr>
<td>error hint</td>
<td>エラーが発生して利用されなかったヒント</td>
</tr>
</tbody>
</table>
<p>pg_hint_plan.debug_print によるログ出力の例を示します。</p>
<pre>LOG:  pg_hint_plan:
        used hint:
        SeqScan(a)
        HashJoin(a b)
        not used hint:
        duplication hint:
        error hint:

STATEMENT:  /*+ HashJoin(a b) SeqScan(a) */
EXPLAIN ANALYZE SELECT * FROM pgbench_branches b
  JOIN pgbench_accounts a ON b.bid = a.bid ORDER BY a.aid;</pre>
<h3>5. まとめ</h3>
<p>本文書では pg_hint_plan について基本的な情報を取りまとめました。PostgreSQL では制御の難しい実行計画を容易に制御可能な pg_hint_plan は強力なツールといえます。<br />
しかしながら、ヒントを利用するかどうかは慎重な検討してください。PostgreSQL のプランナは基本的には賢くかつ網羅的であり、コスト計算が適切であれば様々な実行計画の中から一番適切な実行計画が選択されるはずです。ヒントを指定しても正しい保証は無く、ある状況で適切に思えても他の多くの状況で不適切な計画を実行してしまう危険もはらんでいます。</p>
<h4>◆ 関連サイトリンク</h4>
<ul>
<li>pg_hint_plan プロジェクトページ<br />
<a href="https://github.com/ossc-db/pg_hint_plan" target="_blank" rel="noopener noreferrer">https://github.com/ossc-db/pg_hint_plan</a></li>
<li>pg_hint_plan ドキュメント<br />
<a href="https://github.com/ossc-db/pg_hint_plan/blob/master/README.md" target="_blank" rel="noopener noreferrer">https://github.com/ossc-db/pg_hint_plan/blob/master/README.md</a><br />
(上記README.mdドキュメントは pg_hint_plan 1.4 以降存在しています)</li>
</ul>
<div>
<a href="https://www.sraoss.co.jp/prod_serv/support/pgsql-mainte/?utm_source=sraoss-techblog&utm_medium=referral&utm_campaign=under-article-pgsql" rel="noopener" style="color:initial;" target="_blank" class="no-ext"></p>
<div style="background-color:#DAEFF6; padding:20px; border-radius:8px; text-align:center;">
SRA OSSでは、PostgreSQL/Pgpool-II のサポートを提供しております。<br />
詳しくは<strong style="color:#2581c4;">弊社ホームページ</strong>をご覧ください。
</div>
<p></a>
</div>

<div>
<div style="background-color: #fffacd; padding: 20px; border-radius: 8px; text-align: center;">2026/10/27（火）「PostgreSQL 19 リリース記念セミナー」開催！<br />
コントリビューターが新機能をわかりやすく解説します。<br />
詳しくは<a href="https://www.sraoss.co.jp/event_seminar/detail/20261027-postgresql19-webinar/?utm_source=techblog&amp;utm_medium=email&amp;utm_campaign=pg19-seminar" target="_blank" rel="noopener"><strong style="color: #2581c4;">こちら</strong></a>をご覧ください。</div>
</div>

]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
