【実務・中級編】 外部キー制約 – PostgreSQL

データベースの「守護神」、外部キー制約と上手く付き合う方法

やあ。今日はPostgreSQLの「外部キー(Foreign Key)」について話をしようか。

DB設計を始めたばかりの頃って、ついつい「とりあえずデータが入ればいいや」ってなりがちだよね。でも、実務で数年経ったシステムを触ると痛感するはずだ。「データが壊れている」という地獄がいかに恐ろしいかを。

外部キー制約は、いわばデータベースを守るための「守護神」だ。これがあるだけで、アプリケーションのバグや、うっかりミスによるデータの不整合を未然に防いでくれる。今日は、教科書的な説明よりも、現場で「これだけは知っておけ」というポイントに絞って解説していくよ。

—

そもそも、外部キーは何のためにあるの?

一言で言うなら、「参照整合性(Referential Integrity)」を担保するためだ。

例えば、「ユーザー(users)」テーブルと、「投稿(posts)」テーブルがあるとしよう。`posts`テーブルの`user_id`が、存在しないユーザーIDを指していたらどうなる? アプリ側でエラーが頻発するし、最悪の場合、誰が書いたかわからない「幽霊投稿」がデータベースを汚染することになる。

これを防ぐのが外部キーだ。「`posts.user_id`は必ず`users.id`に存在するものしか許可しない!」とDBに強制させるわけだね。

—

基本の書き方:まずはここから

まずは標準的な定義を見てみよう。

CREATE TABLE users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);

CREATE TABLE posts (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id), — ここが外部キー
content TEXT NOT NULL
);

シンプルだろ? これだけで、`users`に存在しない`id`を`posts`に入れようとすると、PostgreSQLが「おい、そんなユーザーいないぞ!」とエラーを返してくれる。これでデータの平和が守られるわけだ。

—

実務で一番悩む「削除時の挙動」

ここからが本題だ。外部キーを使うとき、一番頭を悩ませるのが「親(users)が削除されたとき、子(posts)はどうするのか?」という問題だ。ここを適当に設定すると、あとで痛い目を見る。

代表的なオプションを紹介するよ。

1. CASCADE(連鎖削除)

親が消えたら、子も道連れにする。

user_id INTEGER REFERENCES users(id) ON DELETE CASCADE

「ユーザーを退会させたら、そのユーザーの投稿もすべて消す」というようなケースだ。一番分かりやすいけど、データを物理的に消すことになるから、安易に使うと「誤操作でデータ全損」という大惨事になりかねない。

2. SET NULL(空にする)

親が消えたら、子の参照先をNULLにする。

user_id INTEGER REFERENCES users(id) ON DELETE SET NULL

「ユーザーは退会したけど、過去の投稿は『退会済みユーザー』として残しておきたい」という時に便利だね。ただし、カラムが`NOT NULL`制約だとエラーになるから注意が必要だ。

3. RESTRICT(拒否)

親が消そうとすると、エラーを投げて止める。

user_id INTEGER REFERENCES users(id) ON DELETE RESTRICT

デフォルトの挙動だ。「まだ投稿が残っているのにユーザーを消すなんて許さない!」とDBが怒ってくれる。実は一番安全で、プロダクション環境ではこれを選択するのが無難なことも多い。

—

先輩からのアドバイス:パフォーマンスと運用の話

最後に、現場でよく出る質問に答えておくよ。

  • 「外部キーを貼ると重くなる?」

確かに制約チェックのオーバーヘッドはある。でも、近年のPostgreSQLなら、通常利用で体感できるほどの負荷はない。データ整合性を担保するコストとしては、極めて安いものだと思っていい。

  • 「外部キーを使わずにアプリ側で制御すればいいのでは?」

これは「禁じ手」だ。アプリのコードがどれだけ優秀でも、誰かがコンソールから直接`psql`でデータをいじったり、別のスクリプトがバグを起こしたりした瞬間に崩壊する。データのルールは、データが住んでいる場所(DB)で守るのが鉄則だ。

  • 「インデックスは必要?」

これは非常に重要だ。外部キーを貼ったカラムには、基本的にはインデックスを貼ることを強く勧める。親テーブルのレコードを削除したり更新したりする際、子テーブル側を全件スキャンすることになるから、インデックスがないと悲惨な遅延が発生するよ。

—

まとめ

外部キーは、最初はちょっと厳格すぎて窮屈に感じるかもしれない。「自由にデータをいじらせてくれよ!」と思うこともあるだろう。

でも、大規模なシステムになればなるほど、この「制約」が君の心を守ってくれる。データが正しいことが保証されているという安心感は、エンジニアにとって何よりの武器になるんだ。

まずは新規開発のテーブル定義から、意識的に`REFERENCES`を書いてみてほしい。もし何かトラブルや疑問が出たら、いつでも相談してくれよ。応援してるぞ。

コメント

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