やあ、今日もデータベースと格闘してる?
PostgreSQLを触り始めてしばらく経つと、「関数(Function)とストアドプロシージャ(Procedure)、結局どっちを使えばいいの?」という疑問にぶつかるはずだ。実際、現場でもこの使い分けが曖昧なまま書かれたコードを見かけることがよくある。
今日は、PostgreSQLの「プロシージャ」について、教科書には書いていない「現場の肌感覚」を交えて深掘りしていこうか。
—
1. 関数とプロシージャの「決定的な違い」はどこ?
まず、一番大切なポイントから話すね。PostgreSQLの関数(`CREATE FUNCTION`)とプロシージャ(`CREATE PROCEDURE`)の最大の違いは、「トランザクションを自分で制御できるか否か」だ。
- 関数: 一つの大きなトランザクションの一部として実行される。途中で `COMMIT` や `ROLLBACK` はできない。
- プロシージャ: プロシージャの中で `COMMIT` や `ROLLBACK` を発行できる。
これが何を意味するか。例えば、「何万件ものレコードを処理するバッチ処理」を想像してみてほしい。関数だと、途中でエラーが起きれば全件ロールバックだし、処理が長すぎればロックの競合でDBが悲鳴を上げる。
プロシージャを使えば、「1,000件処理したらコミットして、また次へ」といった、いわゆる「細切れのトランザクション管理」が可能になるんだ。これは運用保守において、めちゃくちゃ強力な武器になる。
—
2. 実践:プロシージャを書いてみる
まずは基本の構文を見てみよう。
CREATE OR REPLACE PROCEDURE bulk_data_cleanup(batch_size INT)
LANGUAGE plpgsql
AS $$
DECLARE
processed_count INT := 0;
BEGIN
LOOP
— 削除処理
DELETE FROM logs
WHERE id IN (
SELECT id FROM logs
WHERE created_at < NOW() - INTERVAL '30 days'
LIMIT batch_size
);
-- 何も消すものがなければ終了
IF NOT FOUND THEN
EXIT;
END IF;
-- ここで確定させる(関数では絶対にできない!)
COMMIT;
processed_count := processed_count + batch_size;
RAISE NOTICE 'Processed % records...', processed_count;
END LOOP;
END;
$$;
実行するときは `SELECT` ではなく `CALL` を使う。
CALL bulk_data_cleanup(1000);
このコードのポイントは、`COMMIT` をループの中に置いていること。これにより、長時間のトランザクションによるテーブルロックを回避しつつ、安全に大量のデータを処理できるわけだ。
---
3. なぜ「プロシージャ」を選ぶべきなのか?
僕が後輩によく言うのは、「戻り値が必要なら関数、一連の処理の流れ(ワークフロー)を組むならプロシージャ」という使い分けだ。
プロシージャが輝くシチュエーション:
- バッチ処理の統合: アプリケーション側で何度もDBとの往復を繰り返すより、プロシージャ内で完結させた方がネットワークのオーバーヘッドが減る。
- データ移行やクリーンアップ: さっきの例みたいに、トランザクションを切りながら進めたい作業には最適。
- 複雑なビジネスロジックの封じ込め: 「Aを更新して、Bにログを書き、Cを削除する。ただしAの処理が失敗してもログは残したい」といった、例外制御を伴う処理にはプロシージャが向いている。
—
4. 注意点:銀の弾丸ではない
もちろん、何でもプロシージャで書けばいいというわけじゃない。
- デバッグの難しさ: プロシージャ内で複雑な制御をやりすぎると、どこで何が起きているのか追うのが難しくなる。「ビジネスロジックはアプリケーション側に持たせる」のが現代のアーキテクチャの主流であることは忘れないでほしい。
- テスタビリティ: DB内のプロシージャは、アプリケーションコードに比べてユニットテストが書きにくい。CI/CDのパイプラインに組み込む際は、特に注意が必要だ。
—
先輩からのアドバイス
ストアドプロシージャは、データベースという「強力なエンジン」を直接操るためのレバーだ。正しく使えば、アプリケーションのパフォーマンスを劇的に改善できるし、複雑なデータ整合性の維持も容易になる。
でも、「DBの中にロジックを隠しすぎない」という境界線は意識しておこう。DBはデータの守護神であって、アプリケーションのロジックすべてを受け止めるゴミ箱じゃないからね。
もし「これ、関数で書くべきかプロシージャで書くべきか迷うな…」という場面があったら、いつでも相談してくれ。まずは小さなバッチ処理から、プロシージャの「`COMMIT` を操る感覚」を味わってみてほしい。
それじゃ、また。良いコードライフを!
コメント