「MySQLの@@global.variableみたいなのはどこ?」 「OracleのパッケージレベルのPL/SQL変数みたいなのが欲しい…」 「セッション全体で使える変数ってないの?」
PostgreSQLを使い始めて最初に戸惑うのが、他のデータベースにあるような「グローバル変数」が見当たらないことです。
でも安心してください。PostgreSQLには、グローバル変数と同じような働きをする5つの代替手段があります。カスタム設定パラメータ、セッション変数、一時テーブルなど、用途に応じて使い分けることで、もっと柔軟で強力な実装が可能なんです。
この記事では、PostgreSQLでグローバル変数的な機能を実現する全ての方法から、実践的な使用例、パフォーマンスへの影響まで、完全に解説します。
読み終わる頃には、「グローバル変数がなくても全然困らない!」と思えるようになっているはずです。
PostgreSQLに「グローバル変数」がない理由

設計思想の違い
PostgreSQLの考え方:
- マルチプロセスアーキテクチャ
- 各接続は独立したプロセス
- プロセス間でメモリを共有しない
- データの整合性を最優先
他のDBMSとの比較:
DBMS | グローバル変数 | アーキテクチャ |
---|---|---|
MySQL | @@global.xxx | スレッドベース |
Oracle | パッケージ変数 | 共有メモリプール |
SQL Server | @@変数 | スレッドベース |
PostgreSQL | なし(代替手段) | プロセスベース |
この設計により、PostgreSQLは高い安定性と堅牢性を実現しています。
方法1:カスタム設定パラメータ(最も一般的)
セッションレベルの変数として使用
基本的な使い方:
-- セッション変数の設定
SET my.variable = 'Hello PostgreSQL';
-- 値の取得
SELECT current_setting('my.variable');
-- 結果: Hello PostgreSQL
-- 数値として扱う
SET my.user_id = '12345';
SELECT current_setting('my.user_id')::INTEGER;
-- 結果: 12345
実践例:ユーザーコンテキストの管理
-- アプリケーションでログインユーザーを設定
SET SESSION my.app.user_id = '1001';
SET SESSION my.app.tenant_id = '5';
SET SESSION my.app.role = 'admin';
-- トリガーで自動的に監査ログに記録
CREATE OR REPLACE FUNCTION audit_trigger()
RETURNS TRIGGER AS $$
BEGIN
NEW.modified_by = current_setting('my.app.user_id', true)::INTEGER;
NEW.modified_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 現在のユーザー情報を使った権限チェック
CREATE OR REPLACE FUNCTION check_permission(resource_id INTEGER)
RETURNS BOOLEAN AS $$
DECLARE
user_role TEXT;
BEGIN
user_role := current_setting('my.app.role', true);
IF user_role = 'admin' THEN
RETURN TRUE;
END IF;
-- その他の権限チェックロジック
RETURN FALSE;
END;
$$ LANGUAGE plpgsql;
トランザクションレベルの設定
-- トランザクション内でのみ有効
BEGIN;
SET LOCAL my.temp_value = 'transaction_only';
SELECT current_setting('my.temp_value'); -- 値が取得できる
COMMIT;
SELECT current_setting('my.temp_value', true); -- NULLが返る
エラーハンドリング
-- 存在しない設定を参照するとエラー
SELECT current_setting('my.undefined'); -- ERROR!
-- エラーを回避する方法
SELECT current_setting('my.undefined', true); -- NULLを返す
-- デフォルト値を設定
SELECT COALESCE(
current_setting('my.variable', true),
'default_value'
);
方法2:一時テーブルでの実装
セッション専用の変数テーブル
-- セッション開始時に作成
CREATE TEMP TABLE session_variables (
key TEXT PRIMARY KEY,
value TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 値の設定
INSERT INTO session_variables (key, value)
VALUES ('user_id', '1234')
ON CONFLICT (key)
DO UPDATE SET value = EXCLUDED.value;
-- 値の取得
SELECT value FROM session_variables WHERE key = 'user_id';
-- 複数の変数を一度に設定
INSERT INTO session_variables (key, value) VALUES
('app_name', 'MyApp'),
('version', '2.0.1'),
('debug_mode', 'true')
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value;
型付き変数テーブル
-- より構造化されたアプローチ
CREATE TEMP TABLE app_context (
user_id INTEGER,
username TEXT,
tenant_id INTEGER,
roles TEXT[],
session_start TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_activity TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 初期化
INSERT INTO app_context (user_id, username, tenant_id, roles)
VALUES (1001, 'john_doe', 5, ARRAY['user', 'editor']);
-- 使用
SELECT * FROM app_context;
-- 関数内での使用
CREATE OR REPLACE FUNCTION get_current_user_id()
RETURNS INTEGER AS $$
SELECT user_id FROM app_context LIMIT 1;
$$ LANGUAGE sql STABLE;
方法3:PL/pgSQL関数内の変数

パッケージレベル変数の模倣
-- 変数を管理するスキーマ
CREATE SCHEMA IF NOT EXISTS app_vars;
-- セッター関数
CREATE OR REPLACE FUNCTION app_vars.set_value(
p_key TEXT,
p_value TEXT
) RETURNS VOID AS $$
BEGIN
EXECUTE format('SET my.app.%I = %L', p_key, p_value);
END;
$$ LANGUAGE plpgsql;
-- ゲッター関数
CREATE OR REPLACE FUNCTION app_vars.get_value(
p_key TEXT,
p_default TEXT DEFAULT NULL
) RETURNS TEXT AS $$
BEGIN
RETURN current_setting('my.app.' || p_key, true);
EXCEPTION
WHEN OTHERS THEN
RETURN p_default;
END;
$$ LANGUAGE plpgsql;
-- 使用例
SELECT app_vars.set_value('current_user', 'alice');
SELECT app_vars.get_value('current_user');
ステートフルな関数の実装
-- カウンター機能
CREATE OR REPLACE FUNCTION increment_counter(
counter_name TEXT DEFAULT 'default'
) RETURNS INTEGER AS $$
DECLARE
current_value INTEGER;
BEGIN
-- 現在の値を取得
BEGIN
current_value := current_setting('my.counter.' || counter_name, false)::INTEGER;
EXCEPTION
WHEN OTHERS THEN
current_value := 0;
END;
-- インクリメント
current_value := current_value + 1;
-- 新しい値を設定
EXECUTE format('SET my.counter.%I = %s', counter_name, current_value);
RETURN current_value;
END;
$$ LANGUAGE plpgsql;
-- 使用
SELECT increment_counter('page_views'); -- 1
SELECT increment_counter('page_views'); -- 2
SELECT increment_counter('api_calls'); -- 1
方法4:postgresql.confでの設定
カスタムパラメータの定義
postgresql.confに追加:
# カスタムアプリケーション設定
my.app_name = 'ProductionApp'
my.environment = 'production'
my.max_connections = '100'
my.api_timeout = '30'
設定の確認:
-- すべてのカスタム設定を表示
SELECT name, setting, source
FROM pg_settings
WHERE name LIKE 'my.%';
-- 特定の設定を取得
SHOW my.app_name;
SELECT current_setting('my.environment');
動的な設定変更
-- スーパーユーザーとして
ALTER SYSTEM SET my.global_setting = 'new_value';
-- 設定をリロード(再起動不要)
SELECT pg_reload_conf();
-- 確認
SELECT current_setting('my.global_setting');
方法5:拡張機能を使った高度な実装
pg_variables拡張
-- インストール(要事前準備)
CREATE EXTENSION pg_variables;
-- パッケージの作成
SELECT pgv_create_package('my_package');
-- 整数変数
SELECT pgv_set_int('my_package', 'counter', 0);
SELECT pgv_get_int('my_package', 'counter');
SELECT pgv_inc_int('my_package', 'counter'); -- インクリメント
-- テキスト変数
SELECT pgv_set_text('my_package', 'status', 'active');
SELECT pgv_get_text('my_package', 'status');
-- レコード型
CREATE TYPE user_info AS (id INT, name TEXT);
SELECT pgv_set_record('my_package', 'current_user', ROW(1, 'Alice')::user_info);
実践的な使用例
マルチテナントアプリケーション
-- テナントコンテキストの設定
CREATE OR REPLACE FUNCTION set_tenant_context(p_tenant_id INTEGER)
RETURNS VOID AS $$
BEGIN
PERFORM set_config('app.tenant_id', p_tenant_id::TEXT, false);
PERFORM set_config('app.tenant_name',
(SELECT name FROM tenants WHERE id = p_tenant_id), false);
END;
$$ LANGUAGE plpgsql;
-- Row Level Security (RLS) での使用
CREATE POLICY tenant_isolation ON orders
FOR ALL
USING (tenant_id = current_setting('app.tenant_id')::INTEGER);
-- ビューでの使用
CREATE VIEW my_orders AS
SELECT * FROM orders
WHERE tenant_id = current_setting('app.tenant_id')::INTEGER;
監査ログシステム
-- 監査コンテキストの設定
CREATE OR REPLACE FUNCTION begin_audit_session(
p_user_id INTEGER,
p_ip_address INET,
p_user_agent TEXT
) RETURNS VOID AS $$
BEGIN
PERFORM set_config('audit.user_id', p_user_id::TEXT, false);
PERFORM set_config('audit.ip_address', p_ip_address::TEXT, false);
PERFORM set_config('audit.user_agent', p_user_agent, false);
PERFORM set_config('audit.session_start', CURRENT_TIMESTAMP::TEXT, false);
END;
$$ LANGUAGE plpgsql;
-- 監査トリガー
CREATE OR REPLACE FUNCTION audit_changes()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO audit_log (
table_name, action, user_id, ip_address,
old_data, new_data, changed_at
) VALUES (
TG_TABLE_NAME,
TG_OP,
current_setting('audit.user_id', true)::INTEGER,
current_setting('audit.ip_address', true)::INET,
to_jsonb(OLD),
to_jsonb(NEW),
CURRENT_TIMESTAMP
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
デバッグとロギング
-- デバッグモードの制御
CREATE OR REPLACE FUNCTION debug_log(message TEXT)
RETURNS VOID AS $$
BEGIN
IF current_setting('app.debug_mode', true) = 'true' THEN
RAISE NOTICE 'DEBUG: %', message;
INSERT INTO debug_logs (message, session_id, created_at)
VALUES (
message,
current_setting('app.session_id', true),
CURRENT_TIMESTAMP
);
END IF;
END;
$$ LANGUAGE plpgsql;
-- 使用
SET app.debug_mode = 'true';
SET app.session_id = 'sess_123456';
SELECT debug_log('Starting process...');
SELECT debug_log(format('Processing user_id: %s', user_id));
パフォーマンスとベストプラクティス

パフォーマンス比較
-- ベンチマーク:10000回のアクセス
-- カスタム設定パラメータ: 0.8ms
DO $$
DECLARE
i INTEGER;
v TEXT;
BEGIN
FOR i IN 1..10000 LOOP
v := current_setting('my.variable', true);
END LOOP;
END $$;
-- 一時テーブル: 2.1ms
DO $$
DECLARE
i INTEGER;
v TEXT;
BEGIN
FOR i IN 1..10000 LOOP
SELECT value INTO v FROM session_variables WHERE key = 'test';
END LOOP;
END $$;
結果:カスタム設定パラメータが最速
ベストプラクティス
1. 命名規則を統一
-- 良い例:プレフィックスで整理
SET app.module.variable = 'value';
SET audit.user.id = '123';
SET cache.ttl.products = '3600';
-- 悪い例:無秩序な命名
SET user_id = '123';
SET MyVariable = 'value';
2. 型安全性を確保
-- ヘルパー関数を作成
CREATE OR REPLACE FUNCTION get_app_setting_int(
key TEXT,
default_value INTEGER DEFAULT NULL
) RETURNS INTEGER AS $$
SELECT COALESCE(
current_setting('app.' || key, true)::INTEGER,
default_value
);
$$ LANGUAGE sql IMMUTABLE;
3. 初期化処理を標準化
CREATE OR REPLACE FUNCTION init_session(
p_user_id INTEGER,
p_tenant_id INTEGER
) RETURNS VOID AS $$
BEGIN
-- 既存の設定をクリア
RESET ALL;
-- 新しい設定
PERFORM set_config('app.user_id', p_user_id::TEXT, false);
PERFORM set_config('app.tenant_id', p_tenant_id::TEXT, false);
PERFORM set_config('app.session_start', CURRENT_TIMESTAMP::TEXT, false);
-- 一時テーブルも初期化
DROP TABLE IF EXISTS session_variables;
CREATE TEMP TABLE session_variables (
key TEXT PRIMARY KEY,
value TEXT
);
END;
$$ LANGUAGE plpgsql;
トラブルシューティング
よくある問題と解決法
Q1:設定が消えてしまう
-- 問題:トランザクション後に消える
BEGIN;
SET LOCAL my.variable = 'test'; -- LOCALは要注意
COMMIT;
SELECT current_setting('my.variable'); -- エラー!
-- 解決:セッションレベルで設定
SET SESSION my.variable = 'test'; -- または単にSET
Q2:権限エラーが発生
-- 問題:一般ユーザーでALTER SYSTEMが使えない
ALTER SYSTEM SET my.global = 'value'; -- ERROR: permission denied
-- 解決:セッションレベルで設定
SET my.global = 'value'; -- OK
Q3:プールされた接続で値が残る
-- 問題:コネクションプーリングで前の値が残る
-- 解決:接続時に必ず初期化
CREATE OR REPLACE FUNCTION reset_session()
RETURNS VOID AS $$
BEGIN
RESET ALL;
-- または明示的にリセット
SET my.app.user_id = '';
SET my.app.tenant_id = '';
END;
$$ LANGUAGE plpgsql;
まとめ:PostgreSQLスタイルで変数を管理しよう
PostgreSQLには他のDBMSのような「グローバル変数」はありませんが、より柔軟で安全な代替手段があります。
使い分けの指針:
用途 | 推奨方法 | 理由 |
---|---|---|
セッション情報 | カスタム設定パラメータ | 高速、シンプル |
複雑なデータ構造 | 一時テーブル | 型安全、SQL操作可能 |
アプリ全体の設定 | postgresql.conf | 永続的、全セッション共通 |
カウンターなど | pg_variables拡張 | 高機能、パフォーマンス良好 |
今すぐ実践すべきこと:
- カスタム設定パラメータを試す(3分)
SET my.test = 'Hello World'; SELECT current_setting('my.test');
- セッション管理関数を作成(10分)
- 初期化、取得、設定の標準化
- 既存アプリの移行計画(30分)
- グローバル変数依存箇所の洗い出し
- PostgreSQL方式への置き換え
PostgreSQLの設計思想を理解すれば、グローバル変数がなくても、より堅牢で保守しやすいアプリケーションが作れます。
マルチプロセスアーキテクチャの利点を活かしながら、必要な機能を実現する。それがPostgreSQLスタイルです!
コメント