はじめに
PostgreSQLの拡張機能pg_hint_plan
は、実行計画を誘導するための特殊なコメントです。
詳細:pg_hint_planを用いて実行計画を制御する
ところがLinux版と違い、Windows版は公式にビルドしたものをリリースしていません。
別件のトラブルでpg_statsinfo
について調べていると、Windows版のpg_repack
バイナリ配布しているという記事を見つけました。
SRA OSSとしてPostgreSQL拡張のWindowsバイナリ提供を開始しているが、今のところはサポート加入者のみで、一般配布は計画中とのこと。
SRA OSSで継続バイナリリリース
- pg_hint_plan
- pgaudit
- pg_repack
- pg_dbms_stats
※2025年06月時点ではまだ一般配布は行われていません。
じゃー自力でなんとかしてみようと考えたところです。
環境
- Windos 11 Home 64bit
- Visual Studio 2022(C++ によるデスクトップ開発) インストール済み
- PostgreSQL 14.7
仕事の関係上、現時点では少し古めの PostgreSQL のバージョンを使用します。
PostgreSQL 14は、2021/09/30リリース
開発準備
pg_hint_plan
をビルドする上で今回 Visual Studio を使用します。
プロジェクトファイルを Github に置きました。
ただし、pg_hint_plan
のソースコードやpg_hint_plan.dll
は含まれていませんがqueryjumble.obj
のみ含めました。
pg_hint_plan を取得
下記サイトから Source code(zip) を取得します。
筆者は、C:¥WorkSpace に展開しました。
queryjumble.objの取得
pg_hint_plan
をビルドには、「queryjumble.obj」ファイルが必要となります。
PostgreSQLをソースコードからビルドして取得します。
PostgreSQLインストール配下にobj
フォルダを作成し、「queryjumble.obj」ファイルをコピーします。
今回の筆者の場合、PostgreSQLインストールフォルダは「C:¥Program Files¥PostgreSQL¥14」になります。
プロジェクト作成
C++でダイナミックリンクライブラリ(DLL)を選択します。
pg_hint_plan
でプロジェクトを作成します。
プロジェクトを一旦閉じます。
同じ拡張機能のpg_repack
に倣って、pg_hint_plan
フォルダからmsvc
に変更したらプロジェクトを開き直します。
ファイルの追加の既存ファイル削除
上位フォルダから「pg_hint_plan.c」ファイルを追加します。
あと、既存ファイルの下記ファイルは不要なので削除します。
- dllmain.cpp
- pch.cpp
- framework.h
- pch.h
定義ファイルの作成
モジュール定義ファイル(.def)を作成して、msvc
フォルダに配置します。
LIBRARY pg_hint_plan
EXPORTS
_PG_init
_PG_fini
最初は、元ソースコードにPGDLLEXPORT
を追記する方法でビルドしていたのですが、モジュール定義を使用してエクスポート指定すると、元ソースコードの変更せずビルド出来ることが分かりました。
/* Module callbacks */
PGDLLEXPORT void _PG_init(void);
PGDLLEXPORT void _PG_fini(void);
プロパティ設定
pg_hint_plan
でプロジェクトのプロパティにて、構成を「すべての構成」にします。
プリプロセッサ定義
C/C++の「プリプロセッサ」を選択、「プリプロセッサの定義」に「WIN32」を追加します。
PostgreSQLは「WINDLL」を同等のものとして認識しないため、Win32プラットフォームの場合は明示的に指定する必要があります。
※2014年の記事を参考にしているため、もう改善されている可能性があります。
C/C++の例外を無効化
C/C++の「コード生成」を選択、「C/C++の例外を有効にする」で「いいえ」にします。
コンパイルをC言語へ
C/C++の「詳細設定」を選択、「コンパイル言語の選択」で「C コードとしてコンパイル(/TC)」にします。
次に「指定の警告を無視する」を「4703;4996」をセットします。
- 4703…未使用要素
- 4996…非推奨関数
4996の非推奨関数の警告を無視したくない場合、#define _CRT_SECURE_NO_WARNINGS
の指定やstrcpy
の代わりにstrcpy_s
へ書き換える方法もあります。
プリコンパイル済みヘッダーを使用しない
C/C++の「プリコンパイル済みヘッダー」を選択、「プリコンパイル済みヘッダー」で「プリコンパイル済みヘッダーを使用しない」にします。
マニフェストを生成しない
リンカーの「マニフェスト ファイル」を選択、「マニフェストの生成」で「いいえ (/MANIFEST:NO)」にします。
追加の依存関係
リンカーの「入力」を選択、「追加の依存ファイル」に「postgres.lib」と「queryjumble.obj」を追加します。
「親またはプロジェクトの既定値から継承」のチェックは外さないでください。
単純な拡張機能では必要ありませんが、サーバー関数にリンクする場合は必要になります。
モジュール定義関係
モジュール定義ファイル「pg_hint_plan.def」をセットします。
インクルードディレクトリ
C/C++の「全般」を選択、「追加のインクルードディレクトリ」で、PostgreSQLインストール配下の4つのフォルダを追加します。
- include\server\port\win32_msvc
- include\server\port\win32
- include\server
- include
今回の筆者の場合、PostgreSQLインストールフォルダは「C:\Program Files\PostgreSQL\14」になります。
ライブラリディレクトリ
リンカーの「全般」を選択、「追加のライブラリ ディレクトリ」で、PostgreSQLインストール配下のlib
フォルダとobj
フォルダを追加します。
次に「ライブラリ依存関係のリンク」を「いいえ」にします。
obj
フォルダは前述した「queryjumble.obj」用です。
プロジェクトの構築
プログラム
構成
msvc
フォルダの中身、x64¥Releseフォルダに「pg_hint_plan.dll」ファイルが生成される。
修正内容
関数_PG_initと _PG_fini の宣言と定義の両方にPGDLLEXPORTマクロを追加するだけで済みました。
ただし、関数 _PG_init はpostgresが提供するインクルードファイル server\plpgsql.h で宣言されているため、コンパイラエラーpg_hint_plan.c(667,19): error C2375: '_PG_init': redefinition; different linkageを回避するために、関数の宣言にも同様の修正が必要です
https://github.com/ossc-db/pg_hint_plan/issues/201
最初はissues 201に従って元ソースコードを修正する方法だったため、修正方法を記載していたのですがモジュール定義ファイルの方法にしたため、元ソースコードを修正する必要がなくなりました。
ビルド時の不明点
警告として下記メッセージが出たのですが、Grep検索しても関数が見つからないです。
もし何か知っている方がいたら、コメントをお願いします。
'pg_hint_plan_standard_join_search' の関数定義が見つかりません。
'pg_hint_plan_make_join_rel' の関数定義が見つかりません。
'pg_hint_plan_join_search_one_level' の関数定義が見つかりません。
導入
配置
今回の筆者の場合、PostgreSQLインストールフォルダは「C:¥Program Files¥PostgreSQL¥14」になりますので、下記に配置します。
ファイル | 配置先 |
---|---|
pg_hint_plan.dll | lib |
pg_hint_plan.control | share\extension |
pg_hint_plan--1.3.0.sql | 同上 |
pg_hint_plan--1.3.0--1.3.1.sql | 同上 |
pg_hint_plan--1.3.1--1.3.2.sql | 同上 |
pg_hint_plan--1.3.2--1.3.3.sql | 同上 |
pg_hint_plan--1.3.3--1.3.4.sql | 同上 |
pg_hint_plan--1.3.4--1.3.5.sql | 同上 |
pg_hint_plan--1.3.5--1.3.6.sql | 同上 |
pg_hint_plan--1.3.6--1.3.7.sql | 同上 |
pg_hint_plan--1.3.7--1.3.8.sql | 同上 |
pg_hint_plan--1.3.8--1.3.9.sql | 同上 |
pg_hint_plan--1.3.9--1.3.10.sql | 同上 |
pg_hint_plan--1.3.10--1.4.sql | 同上 |
pg_hint_plan--1.4.1--1.4.2.sql | 同上 |
pg_hint_plan--1.4--1.4.1.sql | 同上 |
pg_hint_plan--1.4.2--1.4.3.sql | 同上 |
こんなにsqlファイルが必要なのか、1つでいいのではないかと思ったのですが、pg_hint_plan.control
に書かれたdefault_version
(例: 1.4.3)のように、バージョン3桁のみのファイル(例 1.4.3.sql)が無い場合、バージョン3桁のみファイルから指定バージョンまでアップデートしていく仕組みになっているようです。
postgres.confの修正
設定ファイルにpg_hint_plan
の事前ロード用の設定を追加します。
shared_preload_libraries = 'pg_hint_plan'
ファイル編集後、PostgreSQLサーバを起動、または再起動してください。
※shared_preload_librariesには、カンマ区切りで複数のモジュールが登録できます。
PostgreSQLに拡張機能の登録
CREATE EXTENSION pg_hint_plan;
登録の確認
SELECT extname, extversion
FROM pg_extension
WHERE extname = 'pg_hint_plan';
extname | extversion |
---|---|
pg_hint_plan | 1.4.3 |
検証
検証データ作成
ChatGPTで検証するSQLを作成してもらいました。
CREATE TABLE test_table (
id serial PRIMARY KEY,
name text,
created_at timestamp
);
INSERT INTO test_table (name, created_at)
SELECT
'name_' || s,
now() - (s || ' days')::interval
FROM generate_series(1, 10000) AS s;
CREATE TABLE t1 (id int, val text);
CREATE TABLE t2 (id int, val text);
INSERT INTO t1 SELECT i, 'A' FROM generate_series(1, 1000) i;
INSERT INTO t2 SELECT i, 'B' FROM generate_series(1, 100000) i;
実行プラン基本の確認
ヒント句なしの通常プランの場合、インデックスが効いています。
EXPLAIN SELECT * FROM test_table WHERE id = 1234;
-- 結果
QUERY PLAN
Index Scan using test_table_pkey on test_table (cost=0.29..8.30 rows=1 width=21)
Index Cond: (id = 1234)
EXPLAIN SELECT * FROM test_table WHERE id = 1234;
-- 結果
QUERY PLAN
Index Scan using test_table_pkey on test_table (cost=0.29..8.30 rows=1 width=21)
Index Cond: (id = 1234)
ヒント句でSeqScan
を指定した場合、インデックスが効かなくなっています。
EXPLAIN SELECT /*+ SeqScan(test_table) */ * FROM test_table WHERE id = 1234;
-- 結果
QUERY PLAN
Seq Scan on test_table (cost=0.00..189.00 rows=1 width=21)
Filter: (id = 1234)
実行プラン応用の確認
ヒント句なしの通常プランの場合、Hash Join
やMerge Join
になることが多いです。
EXPLAIN SELECT * FROM t1 JOIN t2 ON t1.id = t2.id;
-- 結果
QUERY PLAN
Hash Join (cost=27.50..1855.50 rows=1000 width=12)
Hash Cond: (t2.id = t1.id)
-> Seq Scan on t2 (cost=0.00..1443.00 rows=100000 width=6)
-> Hash (cost=15.00..15.00 rows=1000 width=6)
-> Seq Scan on t1 (cost=0.00..15.00 rows=1000 width=6)
ヒント句でNestLoop(t1 t2)
を強制した場合
EXPLAIN SELECT /*+ NestLoop(t1 t2) Leading(t1 t2) */ *
FROM t1 JOIN t2 ON t1.id = t2.id;
-- 結果
QUERY PLAN
Nested Loop (cost=0.00..1501460.50 rows=1000 width=12)
Join Filter: (t1.id = t2.id)
-> Seq Scan on t2 (cost=0.00..1443.00 rows=100000 width=6)
-> Materialize (cost=0.00..20.00 rows=1000 width=6)
-> Seq Scan on t1 (cost=0.00..15.00 rows=1000 width=6)
最後に
なんとか、pg_hint_plan
をビルドすることが出来ました。このissue 201がなかったら諦めてたと思います。
今回はPostgreSQL 14でしたが、バージョンごとに導入物を用意する必要があるようです。
SRA OSSで一般配布をして頂けるのがいいですけどね。
Windows版PostgreSQLの拡張機能もサポートがもっと充実するようになればいいのにな。