Query Go
代理キー vs 自然キー — どちらを主キーにすべきか
データモデル設計ガイド

代理キー vs 自然キー — どちらを主キーにすべきか

代理キー(連番・UUID)と自然キー(メール・ISBN)の使い分け早見表。

代理キー vs 自然キー — どちらを主キーにすべきか diagram

代理キーと自然キーとは

自然キー(natural key)は、業務上すでに意味を持つ属性をそのまま主キーにする方式です。メールアドレス、社員番号、ISBN、郵便番号などが典型例です。一方 代理キー(surrogate key)は、業務上の意味を持たない人工的な識別子を別に用意する方式で、BIGSERIAL(連番)や UUID を使うのが一般的です。

設計判断としては「業務キーを主キーにするか、内部 ID を別に立てるか」という問いになります。どちらにも一理あり、実務では代理キーを主キー、自然キーを UNIQUE 制約で併用する折衷案が最も広く使われます。

代理キーの利点

代理キーの最大の利点は「変わらない」ことを保証できる点です。ユーザーのメールアドレスは変更されます。商品コードも採番ルールが変わります。主キーが変わると、それを参照しているすべての外部キーを連鎖更新する必要があり(あるいは ON UPDATE CASCADE に頼る)、トラブルの温床になります。

  • 不変性: 業務上の事情で値が変わらない
  • 短さ・一定サイズ: BIGINT 8 バイト、UUID 16 バイト。複合自然キーより JOIN が軽い
  • プライバシー: URL に /users/42 と出しても個人情報が漏れにくい(メールや電話を直接露出しない)
sql
-- 代理キー + 自然キーの折衷
CREATE TABLE users (
  id         BIGSERIAL PRIMARY KEY,          -- 代理キー
  email      VARCHAR(255) NOT NULL UNIQUE,   -- 自然キー (UNIQUE 制約)
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

自然キーの利点 — JOIN が減る

自然キーにも明確なメリットがあります。業務上の値がそのまま主キーになっているため、外部キーから参照するだけで「何を指しているか」が人間に読める点です。代理キーだと必ず親テーブルへの JOIN が必要ですが、自然キーなら子テーブルの外部キー列を直接読めば済みます。

例: 国コード(ISO 3166-1 alpha-2)や通貨コード(USD, JPY)、言語コード(en, ja)のような国際標準で不変な短い識別子は、自然キーにする価値があります。orders.currency = 'JPY' と書けるのは、currency_id = 3 よりはるかに読みやすいからです。

UUID にすべきか、連番にすべきか

代理キーを採用する場合、BIGSERIAL(連番)と UUID のどちらを選ぶかが次の判断になります。

  • 連番(BIGSERIAL / AUTO_INCREMENT): 8 バイト、B-Tree インデックスが連続挿入で効率的。ただし「次の ID」が推測可能で、分散システムでは中央採番が必要
  • UUID v4: 16 バイト、分散環境でも衝突なく生成可能。反面、ランダムな値なので B-Tree のページ分割が増え、挿入性能とキャッシュ効率が落ちる
  • UUID v7 / ULID: 時系列順に並ぶ UUID。v4 の欠点を解消しつつ分散生成も可能。新規プロジェクトなら第一候補

「絶対に連番 ID を URL に出したくない」「マルチリージョンで採番したい」などの要件がなければ、BIGSERIAL で始めて困ったら移行でも十分です。

自然キーの罠 — 後から必ず変わる

「社員番号は絶対に変わらない」「マイナンバーは一意で不変」と言われたはずの値が、合併・制度変更・入力ミス訂正などで後から変わるのは日常茶飯事です。自然キーを主キーにしていると、このとき外部キー参照がすべて壊れるか、連鎖更新で巨大なロックが発生します。

さらに複合自然キー(例: (company_code, employee_no))は、すべての子テーブルで同じ複合列を引き回すことになり、スキーマが肥大化します。

実務ルール: 主キーは代理キー、自然キーは UNIQUE 制約で別途保証する。これで両方の利点を取れます。