主キー設計入門 — AUTO_INCREMENT と UUID をどう選ぶか

概要

テーブルを作るときに必ず決めるのが「主キー(Primary Key)をどう採番するか」です。最も一般的な選択肢は次の2つです。

  • AUTO_INCREMENT(連番): 1, 2, 3, ... とDBが自動で振る整数キー
  • UUID: 550e8400-e29b-41d4-a716-446655440000 のような128bitの一意な識別子

どちらも「行を一意に識別する」という役割は同じですが、性能・分散のしやすさ・セキュリティ面で性質が大きく異なります。本記事では入門者向けに、両者の仕組み・トレードオフ・現実的な選び方を整理します。

それぞれの基本

AUTO_INCREMENT(連番)

-- MySQL
CREATE TABLE users (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  name VARCHAR(255) NOT NULL,
  PRIMARY KEY (id)
);
-- PostgreSQL(IDENTITY 列。SERIAL より推奨)
CREATE TABLE users (
  id BIGINT GENERATED ALWAYS AS IDENTITY,
  name VARCHAR(255) NOT NULL,
  PRIMARY KEY (id)
);

DBがカウンタを持ち、INSERT のたびに単調増加する整数を割り当てます。

UUID

-- PostgreSQL(v4 = ランダム)
CREATE TABLE users (
  id UUID NOT NULL DEFAULT gen_random_uuid(),
  name VARCHAR(255) NOT NULL,
  PRIMARY KEY (id)
);
-- MySQL 8.0 では BINARY(16) 格納が定石
CREATE TABLE users (
  id BINARY(16) NOT NULL,
  name VARCHAR(255) NOT NULL,
  PRIMARY KEY (id)
);

アプリ側でもDB側でも生成でき、事前にDBへ問い合わせなくても一意なIDを作れるのが特徴です。

比較表

観点 AUTO_INCREMENT UUID
サイズ 8バイト(BIGINT) 16バイト(BINARY(16))
一意性の保証 単一DB内 実質グローバル
事前採番 不可(INSERT後に確定) 可能(クライアント側で生成可)
推測しにくさ 連番なので推測容易 推測困難
INSERT時の挿入位置 常に末尾 ランダム(v4)
ソート・範囲検索 作成順に近い v4は不可 / v7は可能
分散DB・シャーディング 衝突しやすい 衝突しにくい

AUTO_INCREMENT のメリット・デメリット

メリット

  • サイズが小さくインデックス効率が良い
  • 単調増加するため、クラスタインデックス(InnoDB)末尾に追記され、ページ分割が起きにくく書き込みが速い
  • 人間が読みやすくデバッグしやすい

デメリット

  • URLに /users/1234 のように出すと、件数やIDが推測される(列挙攻撃・情報漏洩のリスク)
  • 複数DBを統合・シャーディングするときに採番が衝突する
  • INSERT するまで値が確定しない

UUID のメリット・デメリット

メリット

  • アプリ側で採番でき、DBへ問い合わせる前にIDを確定できる(バルクINSERT・イベント駆動と好相性)
  • グローバルに一意で、分散システム・オフライン生成・複数サービス統合に強い
  • 連番でないため外部露出しても件数を推測されにくい

デメリット(特に v4)

  • サイズが大きい(文字列で持つと36バイトでさらに悪化)
  • ランダムな v4 はクラスタインデックス上で挿入位置がバラつき、ページ分割とインデックス肥大化を招く
  • 作成順にソートできない

v4 の性能問題と UUID v7

UUID v4 の「ランダムゆえにインデックス末尾に追記できない」問題を解決するのが UUID v7 です。v7 は先頭にミリ秒精度のタイムスタンプを持つため、生成順にほぼ単調増加します。

v4: 550e8400-e29b-41d4-a716-446655440000  ← ランダム、順序性なし
v7: 018f3e7a-9c2b-7xxx-xxxx-xxxxxxxxxxxx  ← 先頭がタイムスタンプ、生成順に増加

これにより「UUIDの分散・推測困難性」と「連番のインデックス効率」を両立でき、時系列ソートも可能です。新規設計でUUIDを採用するなら、v4よりv7を検討する価値があります。

実装例:MySQL で UUID を効率よく持つ

MySQL では CHAR(36) の文字列ではなく BINARY(16) に変換して格納するのが定石です。

INSERT INTO users (id, name)
VALUES (UUID_TO_BIN(UUID(), 1), 'Alice');
-- 第2引数 1 でタイムスタンプ部分を先頭に並べ替え、インデックス効率を改善

SELECT BIN_TO_UUID(id, 1) AS id, name FROM users;

CHAR(36) で持つとサイズは倍以上になり、インデックスも重くなります。

現実的な選び方

  • 単一DB・性能とシンプルさ優先 → AUTO_INCREMENT
  • URLやAPIにIDを露出し推測されたくない → UUID(または連番の難読化)
  • 分散・シャーディング・複数サービス統合・オフライン生成 → UUID(v7推奨)
  • 迷ったら: 内部結合は連番、外部公開はUUIDの「ハイブリッド」
CREATE TABLE orders (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,  -- 内部結合用
  public_id BINARY(16) NOT NULL,               -- 外部公開用
  PRIMARY KEY (id),
  UNIQUE KEY uk_public_id (public_id)
);

確認方法

-- MySQL: テーブル/インデックスサイズの確認
SELECT table_name,
       ROUND(data_length/1024/1024, 1) AS data_mb,
       ROUND(index_length/1024/1024, 1) AS index_mb
FROM information_schema.tables
WHERE table_schema = DATABASE();

同じ件数を AUTO_INCREMENT テーブルと UUID v4 テーブルに投入し、index_mb を比較すると v4 の肥大化が確認できます。

注意点

  • 「UUIDは遅い」は主に v4 × ランダム挿入 × クラスタインデックス の話。v7や BINARY(16) 格納で大きく改善する
  • UUIDを VARCHAR/CHAR(36) で持つのはサイズ・性能の両面で不利
  • 主キーは後から変えづらい。外部露出の有無・分散の予定を初期に見極める

まとめ

  • AUTO_INCREMENT は小さく速くシンプル。単一DBのデフォルト候補
  • UUID はグローバル一意・事前採番・推測困難が強み。分散や外部露出に強い
  • v4の性能問題は v7 と BINARY(16) 格納で緩和できる
  • 「内部は連番・外部はUUID」のハイブリッドも実務では有力

主キー設計は後から直しにくい土台です。要件(外部露出・分散・書き込み量)を先に見極めて選びましょう。

\ 最新情報をチェック /

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください