投稿

Primary Key はどう選ぶ?Auto Increment・UUID・自然キーの設計比較

データベースの Primary Key は Auto Increment、UUID、それとも自然キーにするべきか。性能・安全性・拡張性の観点から実務的に比較します。

Primary Key はどう選ぶ?Auto Increment・UUID・自然キーの設計比較

データベース設計を始めると、かなり早い段階でぶつかるのが「Primary Key は何を使うべきか」という問題です。

Primary Key には一意性が必要なので、最初は「もともと重複しない値をそのまま使えばいいのでは」と考えがちです。

たとえば、各個人に一意に割り当てられるマイナンバーを、そのまま Primary Key にしてよいのでしょうか。

ここで大事なのは、「Primary Key にできるか」と「Primary Key に向いているか」は別の話だという点です。

この記事では、Primary Key によく使われる選択肢を整理しながら、それぞれのメリットと注意点を実務目線で見ていきます。

業務上の値を Primary Key にしてよいか?

結論から言えば、本当に一意性を保証できる列であれば、技術的には Primary Key にできます。

ただし実務では、業務上の値をそのまま Primary Key にするのはあまり勧められません。主な理由は、性能、セキュリティ、そして将来の拡張性です。

1. インデックス性能

マイナンバーのような文字列カラムは、整数よりも長くなりやすく、比較コストやインデックスサイズにも影響します。

一般的な B+ Tree インデックスでは、短くて単調増加する整数値のほうが、文字列ベースの Primary Key より効率的なことが多いです。これが Auto Increment の ID が今でもよく使われる理由の一つです。

2. プライバシーとセキュリティ

Primary Key は URL、API レスポンス、ログ、検索条件、外部システム連携など、さまざまな場所に現れます。

そこにマイナンバー、電話番号、社員番号のような業務上の意味を持つ値や個人情報を使ってしまうと、システムの中核にセンシティブな情報を置くことになります。セキュリティや監査の観点では、あまりよい設計とは言えません。

3. 拡張性

性能やセキュリティ以前に、あとから本当に困るのは拡張性であることも多いです。

たとえば最初は「1人につき1アカウント」という前提で、マイナンバーをそのままゲームアカウントの Primary Key にしたとします。

しかし将来、業務要件が変わって「1人で3アカウントまで持てる」ようになったら、その設計はすぐに苦しくなります。

しかも、その Primary Key がすでに他のテーブルから外部キーで参照されていたら、スキーマ全体の見直しが必要になり、修正コストはかなり大きくなります。

では、何を Primary Key にするのがよいか?

業務上の列をそのまま Primary Key にしないのであれば、実務でよく選ばれるのは次の二つです。

  1. Auto Increment の連番
  2. UUID

Auto Increment の連番

Auto Increment の ID は、もっとも古典的で、今でも非常によく使われる Primary Key の設計です。

SQL Server では IDENTITY、PostgreSQL では SEQUENCEGENERATED AS IDENTITY が代表的です。

メリットはわかりやすく、次のような点があります。

  1. 値が短く、インデックス効率がよい
  2. 連続した挿入になりやすく、B+ Tree インデックスと相性がよい
  3. 多くの場合データベース側で自動生成でき、アプリケーション側の管理が少なくて済む

単一データベースのモノリス構成や、保存前に ID を先に発行する必要がないシステムでは、Auto Increment は非常に堅実な選択です。

UUID

UUID の大きな利点は、データベースの連番に依存しないことです。アプリケーション側でもデータベース側でも生成できるため、分散システム、マイクロサービス構成、オフラインで先に ID を発行したいケースと相性がよいです。

UUID はアプリケーションでもデータベースでも生成できますが、利用できる関数や書き方はデータベースごとに異なります。

代表的な UUID のバージョンは次の三つです。

  • v1: タイムスタンプと MAC アドレスをもとに生成
  • v4: 乱数ベースで生成され、現在もっとも一般的
  • v7: 時系列で並びやすく、インデックスに優しい

データベースごとの UUID 生成方法は、おおむね次の通りです。

データベースUUID の生成関数返却形式
PostgreSQLgen_random_uuid()ハイフン付きの標準文字列
MySQLUUID()ハイフン付きの標準文字列
SQL ServerNEWID()UNIQUEIDENTIFIER
Oracle (旧)SYS_GUID()ハイフンなしの RAW(16)
Oracle (23ai)UUID()標準準拠の RAW(16)

Oracle では、UUID を VARCHAR2(36) ではなく RAW(16) で保持したほうが、使用容量を 50% 以上削減でき、インデックス効率もよくなることが多いです。

UUID は本当に重複しないのか?

結論だけ言えば、理論上は重複し得ますが、現実にはほぼ無視できるレベルです。

考えるポイントは三つあります。

1. どれくらい低確率なのか?

一般的な UUID v4 にはおよそ $2^{122}$ 通り、つまり約 $5.3 \times 10^{36}$ の組み合わせがあります。

この数は非常に大きく、たとえ毎秒 10 億個の UUID を 100 年生成し続けたとしても、衝突確率はほぼ無視できます。

2. 実際に重複が起きるとしたら、原因は何か?

現実に UUID の重複が起きる場合、UUID 空間が足りないというより、乱数生成側に問題があることが多いです。

たとえば次のようなケースです。

  1. 乱数シードの再利用
  2. ライブラリ実装の不備
  3. 十分に信頼できる乱数源を使っていない

3. それでも衝突が気になるなら?

金融、会計、高信頼システムのように衝突リスクを特に気にする領域では、少なくとも次のような対策が取れます。

  1. データベース側で一意制約を付ける
  2. 必要に応じて UUID v7 や ULID のような、より順序性のある方式を使う

Auto Increment を使う場合、推測されやすさはどう防ぐか?

Auto Increment を避けたい理由としてよく挙がるのが、「連番だと推測しやすい」という点です。

たとえば URL が /user/1001 なら、/user/1002/user/1003 を試したくなるのは自然です。ただし本当に解決すべき問題は、「Primary Key が連番であること」ではなく、「認可を Primary Key だけに依存してはいけない」という点です。

外部から見たときに連番の規則性を弱めたいだけなら、次のような方法があります。

  1. テーブルの Primary Key を外部にそのまま出さない
  2. 公開用の ID を別に持つ
  3. 必要に応じて Hashids、ULID、UUID を外部識別子として使う

Hashids は非常によく使われるライブラリで、PHP、Python、JavaScript、Go、Java など多くの言語に実装があります。整数の ID を Salt を使って短い文字列に変換し、たとえば 1 -> jR のように表現できます。EC サイトで注文番号を単純な連番のまま公開すると、近い番号から 1 日の注文件数を推測されやすくなります。Hashids を使っても、Salt が弱かったり、十分なサンプルが集まったりすると、元の規則性を推測される可能性は残ります。

ただし、こうした方法で改善できるのは見えやすさや推測しやすさであって、認可そのものではありません。実際の安全性は、必ずバックエンドの認可チェックで担保する必要があります。

実務ではどう選ぶべきか?

ひとことで整理すると、だいたい次のように考えればよいです。

  1. 単一 DB のモノリスで性能重視なら Auto Increment を優先する
  2. 分散システムや複数サービス構成で、保存前に ID が必要なら UUID を検討する
  3. 業務上の意味を持つ値や個人情報を、そのまま Primary Key にしない

理想的な Primary Key は、安定していて、単純で、業務上の意味を持たず、要件変更によってスキーマ全体の見直しを招きにくいものです。

多くの場合、本当に業務上の意味を持たせたい列は、Primary Key にするのではなく、別カラムとして保持したうえで一意制約を付けるほうがうまくいきます。

この投稿は投稿者によって CC BY 4.0 の下でライセンスされています。

トレンドのタグ