【実務・中級編】 ストアドプロシージャの基礎 – PostgreSQL

やあ、今日もデータベースと格闘してる?

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` を操る感覚」を味わってみてほしい。

それじゃ、また。良いコードライフを!

コメント

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