えっ、PostgreSQLにグローバル変数ってないの!?代替手段と賢い使い方完全ガイド

データベース・SQL

「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拡張高機能、パフォーマンス良好

今すぐ実践すべきこと:

  1. カスタム設定パラメータを試す(3分) SET my.test = 'Hello World'; SELECT current_setting('my.test');
  2. セッション管理関数を作成(10分)
    • 初期化、取得、設定の標準化
  3. 既存アプリの移行計画(30分)
    • グローバル変数依存箇所の洗い出し
    • PostgreSQL方式への置き換え

PostgreSQLの設計思想を理解すれば、グローバル変数がなくても、より堅牢で保守しやすいアプリケーションが作れます。

マルチプロセスアーキテクチャの利点を活かしながら、必要な機能を実現する。それがPostgreSQLスタイルです!

コメント

タイトルとURLをコピーしました