1️⃣

「IDをPKとしてよいか」について考える

2025/02/02に公開

1. はじめに

Webアプリケーション開発でデータを扱う際、これまで基本的にデータベース(特にRDB)を使用してきました。しかし、その中で「どのように主キー(PrimaryKey)を設定するか」は、基本的にDB側でauto incrementされるものを使用しオブジェクト固有のIDなどはあまり使用せずにきました。

本記事では、

  • 「IDをPKとするのはよいか?」
  • その際に生じる アンチパターンよいパターン
  • 実際に適用する際のユースケース

について整理していきます。


2. 主キー(PrimaryKey)とは

  • RDBにおいて、あるテーブルを一意に識別するための列(または複数列の組み合わせ)
  • 通常は「絶対に重複がない」「NULLではない」という制約を課し、テーブル内のエントリをユニークに区別する
  • アプリケーションでは、主キーを用いて行を参照・更新・削除するため、検索性能データ整合性に影響


3. 「IDをPKとする」ことの前提

  • アプリケーション全体で一意になるIDを採番する場合が多い(連番、UUID、ULIDなど)
  • DB上では、1テーブル1レコードを識別するための主キー(一意の列)としてこのIDを設定する場合がある
  • 「IDをPKとして使う」場合のいくつかのアンチパターンや注意点がある


4. アンチパターン

アンチパターン1: 業務上の自然キー(メールアドレス・電話番号など)をそのままPKにする

  • : ユーザーテーブルの主キーを email 列にしている
  • 問題点:
    1. 変更リクエスト: メールアドレスや電話番号は変更される可能性がある
    2. 重複リスク: ユーザーの入力ミスや使い回しなどで、重複や一意性が崩れるリスク
    3. 長さ: 文字列が長いため、PKとしての検索/結合パフォーマンスが悪化しやすい
  • 回避策:
    • 変更の可能性がある列はPKにしない。別途、一意制約(UNIQUE KEY) を張る程度にとどめる


アンチパターン2: 業務ドメイン情報を複数列で組み合わせてPKにする(複合主キー)

  • : 売上伝票テーブルで (店舗コード + 伝票番号 + 日付) を複合PKにしている
  • 問題点:
    1. 変更・拡張コスト: 将来的に「別の情報を含めたい」など要件変更があった時、PK設計を変えると大きく影響が及ぶ
    2. 意図しない衝突: 店舗コードの体系変更、日付の扱いミス、伝票番号の管理不備などで衝突が起こりやすい
    3. JOINの煩雑さ: 複数列の結合キーを用いるとクエリが長くなり可読性が落ちる
  • 回避策:
    • 業務上の識別子は別カラムとして保持し、単一のサロゲートキー(人工的に生成したID) をPKに使うのが一般的


アンチパターン3: 外部システムのIDをそのままPKにする

  • : 他システム(A社APIなど)が払い出す「ユーザーID」をそのままローカルDBの主キーにしている
  • 問題点:
    1. 変更リスク: 外部システムがID体系を変更した場合、こちらのDBにも影響が及ぶ
    2. 管理外: 衝突やIDの欠番が起きても、こちらのシステムでは制御できない
    3. 依存度: 外部システムとの結合度が高くなり、マイクロサービス的には柔軟性が下がる
  • 回避策:
    • ローカル側で別のID(PK)を採番 し、外部システムのIDはあくまで「参照用カラム」として保持


アンチパターン4: 意味を持たせすぎたPKを無理やり使う(ドメインに深く結びつける)

  • : ORDER-20230101-000123 のような文字列をそのままPKにしつつ、アプリケーションのロジックのあちこちでパースして利用
  • 問題点:
    1. 変更不能な仕様: フォーマットが少しでも変わると既存データも更新が必要
    2. PKをパースしないと意味が分からないロジック: 解析処理が増える、メンテナンスが煩雑
    3. 検索性能: 文字列型PKは索引効率が悪い場合がある
  • 回避策:
    • 業務上の識別フォーマット(注文番号など)は別カラムに持ち、単純かつ短いPK(例えば数値IDやUUID等)を用いる


アンチパターン5: PKが変更される(可変PK)

  • : カラムを主キーにしているが、ユーザー都合で値が変更される
  • 問題点:
    1. 参照元もすべてアップデートが必要: 外部キーを貼っているテーブル、キャッシュ、ログなど影響範囲が大きい
    2. IDの一意性破壊リスク: 過去に使われた値が再利用されるかもしれない
    3. DB・アプリロジックの複雑化: PKを変更するときに大きなトランザクションやロジックを要する
  • 回避策:
    • PKは不変(Immutable) であるべき。 更新される可能性がある列はPKに採用しない


5. よいパターン

パターン1: サロゲートキー (連番 or シーケンス) + 業務キーは別カラム

  • :
    • id (INT AUTO_INCREMENT) をPKとする
    • order_number (VARCHAR)に独自の注文番号を格納
  • メリット:
    1. PKは変わらない・シンプルな整数、検索やJOINが高速
    2. 業務キー(注文番号など)の変更・拡張があってもPKに影響しない
    3. 大半のRDBで実装が簡単(AUTO_INCREMENT, IDENTITY, SEQUENCEなど)
  • 適したユースケース:
    • 小~中規模のモノリシックアプリや、データベースのシャーディングを強く考慮しない場面
    • ユーザーに連番を見せたいケース(ただし実際にPKをユーザーには見せず、別カラム表示とする方が望ましいことも多い)
    • 連番のためURLが推測されやすいといった問題点を受け入れられる場合


パターン2: サロゲートキー (UUID / ULID など) + 業務キーは別カラム

  • :
    • id (CHAR(36)) or id (CHAR(26)) にUUIDやULIDを格納
    • order_number (VARCHAR)に独自フォーマットの注文番号を格納
  • メリット:
    1. グローバル一意性: 分散システムや複数サービスで重複しにくい
    2. DBへの依存度低減: アプリケーション側でIDを生成可能
    3. ULIDなら時系列でソート可能、ログなどの解析がしやすい
  • 適したユースケース:
    • マイクロサービス構成で複数のサービスやデータベースでID生成を行いたい場合(UUID/ULIDなら衝突しにくい)
    • 大量のINSERTがあり、DB側で連番を発行するとボトルネックになる可能性がある場合アプリ側でUUID/ULIDを生成することでDB負荷を軽減可能
    • 業務キーが可変・複雑・拡張されやすい場合
  • 適さないユースケース:
    • DBのインデックスコストを極限まで抑えたい場合
      • 超大規模データで文字列キー自体がボトルネックになるなどどうしても文字列を避けたいときは、整数型の連番を使うケースもある。


パターン3: サロゲートキーをPKとし、業務ドメインの自然キーにUNIQUE制約

  • :
    • id (BIGINT) → PK (AUTO_INCREMENT / シーケンスなど)
    • email (VARCHAR) UNIQUE NOT NULL → 「メールアドレス」で特定ユーザーの重複を避けたい
  • メリット:
    1. PKはシンプルな数値でJOINパフォーマンスがよい
    2. メールアドレスは変更されうるが、重複は許容しない → UNIQUE制約
    3. アプリケーションロジックとしては「メールアドレス重複エラー」が発生した際にハンドリング可能
  • 適したユースケース:
    • ユーザーが登録時に同じメールアドレスを使えないようにしたい
    • ただしメールアドレス自体が変わることもある → PKにはしない


パターン4: 業務ドメインで確実に不変かつ一意な自然キーがある場合のみ、自然キーをPKにする

  • :
    • 国際標準化されたISBN(本の国際標準図書番号)など、絶対に重複・変更されない保証がある場合に限る
    • 金融の国際コード(ISIN)など
  • メリット:
    1. 自然キーそのものが厳格に管理されているので、余計なID管理が不要
    2. ドメインとDBのキーが1対1でシンプル
  • 注意点:
    • 本当に変更・再利用のリスクがないのかを厳密に確認する
    • 外部要因で運用ルールが変わるケース(ISBN体系変更など)もあるので、将来を見据える必要がある


6. ユースケース別のまとめ

  1. 小規模なWebアプリ (モノリシック)

    • パターン1(AUTO_INCREMENT) が最も簡単。
    • 業務上のキーは別カラムか、UNIQUE制約で対応。
  2. マイクロサービス構成の中規模~大規模システム

    • パターン2(UUID/ULID) でIDをアプリ側生成 → DB負荷や衝突を低減
    • データ連携先にも衝突なく運用可能
    • 人間が扱うIDは短くて覚えやすい業務キーとして別カラムに切り出すことでUUID/ULIDの可読性の低さを回避する。
  3. 既存システムとの連携がある場合

    • 外部システムのIDは「参照キー」として保持
    • パターン1 or 2 でローカルPKを保持し、結合度を下げる
  4. 自然キーが厳密に定まっていて不変かつ世界的にユニーク

    • パターン4 を採用
    • ただし、本当に変更リスクがないかを慎重に検討


7. まとめ

  • IDをPKにすること自体は一般的かつ有効 だが、

    • 業務上の自然キーを直接PKにする → 変更リスク・重複リスク
    • 複数カラムの複合キー → 設計・変更が複雑化
    • 外部システムのIDをそのまま → 依存度増大&変更リスク
    • …などのアンチパターンに陥らないよう注意する
  • よいパターンとしては、

    • シンプルなサロゲートキー(連番・UUID/ULIDなど)をPKに置き、業務上のキーは別で管理
    • どうしてもドメイン固有の自然キーを使うなら、本当に不変でユニークか慎重に確認する
    • 不変でない情報や外部由来の情報はPKにしない
  • 実装のポイント:

    1. PKは不変であること
    2. 業務キーに変更や重複リスクがあるなら、PKは別途用意する
    3. アンチパターンを避ければ、将来的な仕様変更や拡張にも柔軟に対応できる

これらを踏まえて、主キー設計に意図をもって取り組みたい。

Discussion