MySQL の外部キーとリレーション

外部キー(FOREIGN KEY)はテーブル間のリレーションをデータベースレベルで保証する仕組みです。関連するテーブル同士の整合性を自動的に維持してくれるため、不整合なデータの混入を防げます。

外部キーの基本

外部キーは、あるテーブルのカラムが別のテーブルの主キーまたはユニークキーを参照することを宣言します。

CREATE TABLE departments (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(100) NOT NULL
);

CREATE TABLE employees (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(100) NOT NULL,
  department_id INT NOT NULL,
  FOREIGN KEY (department_id) REFERENCES departments(id)
);

この定義により、employees テーブルの department_id には departments テーブルに存在する id しか入れられなくなります。存在しない部署 ID を指定すると、エラーになります。

リレーションの種類

テーブル間の関係は、大きく3つのパターンに分類されます。

1対多(One to Many)

最も一般的なリレーションです。1つの部署に複数の社員が所属するケースがこれにあたります。「多」側のテーブルに外部キーを配置します。

1対1(One to One)

ユーザーとプロフィールのように、各行が1対1で対応する関係です。外部キーに UNIQUE 制約を付けることで実現します。

多対多(Many to Many)

学生とコースのように、双方向に複数の関連がある関係です。中間テーブル(結合テーブル)を挟んで2つの1対多リレーションとして表現します。

ON DELETE と ON UPDATE

外部キーで重要なのが、参照先のデータが削除・更新されたときの振る舞いを指定するオプションです。

オプション削除時の動作更新時の動作
RESTRICT削除を拒否更新を拒否
CASCADE子も一緒に削除子のキーも更新
SET NULL子のキーを NULL に子のキーを NULL に
CREATE TABLE orders (
  id INT AUTO_INCREMENT PRIMARY KEY,
  user_id INT,
  total DECIMAL(10,2) NOT NULL,
  FOREIGN KEY (user_id) REFERENCES users(id)
    ON DELETE SET NULL
    ON UPDATE CASCADE
);

この例では、ユーザーが削除されると注文の user_id が NULL になり、ユーザーの id が変更されると注文の user_id も自動的に更新されます。

CASCADE の注意点

ON DELETE CASCADE は便利ですが、意図しない大量削除を引き起こす危険があります。親テーブルの1行を削除したら、子テーブルの数万行が連鎖的に消えるケースも起こりえます。

CASCADE が安全な場面

ブログ記事を削除したらコメントも消す、ユーザーを削除したらセッション情報も消す、など子データが親に完全に従属している場合。

CASCADE が危険な場面

ユーザーを削除したら注文履歴も消す、のようにビジネス上残すべきデータまで削除してしまう場合。この場合は RESTRICT にして、アプリケーション側で制御するのが安全です。

外部キーを使わない設計

大規模なシステムやマイクロサービスでは、外部キー制約をあえて使わないという選択もあります。外部キーは INSERT/UPDATE/DELETE のたびに参照先の存在チェックが走るため、高負荷環境ではパフォーマンスのボトルネックになることがあります。

その場合は、アプリケーション層で整合性を担保し、定期的なバッチ処理で不整合データを検出・修正するアプローチを取ります。ただし、この方針はチーム全体の規律が求められるため、小〜中規模のプロジェクトでは素直に外部キーを使うのが賢明です。