0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

1,2,3,4...とカウントし続けるAutoIncrementは本当に必要?みんな使ってるけど実はちゃんと使われていない説

Last updated at Posted at 2025-06-30

多くの開発者が何気なく使っているAutoIncrementについてみなさんどう思いますか?

「え,AutoIncrementって便利だし,みんな使ってるじゃん?」って思った方,まさにその通り!でも,実は現代のWebサービスには結構危険な存在になってしまったのではないでしょうか?

目次

  1. そもそもAutoIncrementって何?
  2. AutoIncrementの「え,マジで?」な問題点
  3. 救世主たちの登場!代替案
  4. 実際にコードで書いてみよう
  5. 結局どれを選べばいいの?

そもそもAutoIncrementって何?

AutoIncrementは,データベースの「番号札システム」みたいなものです.銀行の順番待ちでらう番号札を想像してみてください.

CREATE TABLE products (
    id INT AUTO_INCREMENT PRIMARY KEY,  -- これが番号札!
    name VARCHAR(255) NOT NULL,
    price DECIMAL(10, 2) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

新しい商品を追加するたびに,1,2,3,4...と自動で番号が振られていきます.「簡単でいいじゃん!」と思いますよね.

AutoIncrementの「え,マジで?」な問題点

1. スケールすると大パニック!

複数店舗で同じ番号札を配る悲劇

想像してみてください.全国に展開している銀行で,各支店が独立して番号札を配っているとします...

東京支店: 1番,2番,3番,4番...
大阪支店: 1番,2番,3番,4番...  ← あれ?同じ番号!?

これがまさにシャーディング(データベース分散)で起こる問題です.各データベースサーバーが勝手に同じIDを生成してしまうんです.

マスター・スレーブ構成で起こる「誰が本物?」問題

複数のデータベースサーバーが同期を取ろうとしたとき,同じIDで違うデータが作られちゃう可能性があります.もう混乱の極みです.

2. セキュリティが丸見え状態

これが一番「えー!」ってなる問題かもしれません.

あなたのサービス,データ丸見えです

連続した数字のIDは,あなたのサービスについて色々なことを教えてしまいます:

  • 「うちの会員数バレてる!」: ID 10000と20000の差を見れば,約10000人の会員がいることがわかっちゃいます
  • 「成長率もバレてる!」: 1週間でIDが1000から1500に増えたら,「週500人ペースで成長してるんだな」とわかります
  • 「他人のデータが見放題!」: URLのIDを変えるだけで,他のユーザーのページにアクセスできちゃう可能性が...
# 悪意のあるユーザーの思考
https://yoursite.com/users/1001  ← 自分のプロフィール
https://yoursite.com/users/1002  ← 隣の番号も見てみよう!
https://yoursite.com/users/1003  ← こっちも!

怖くないですか?

実際にあった怖い話

某サービス(私の自作のサービス...未公開ですが....)で,ユーザーIDが連番だったせいで:

  • 競合他社に正確なユーザー数を把握された
  • URLのIDを変更するだけで他ユーザーの個人情報が見えた()
  • サービスの成長率や活動度が筒抜けになった

なんてことが実際に起こりました.これは認証をしっかりすれば回避はできると思いますが...データベース側でも意識したいセキュリティ事項ではありますね!

3. パフォーマンスも実は微妙

人気ラーメン店の行列問題

AutoIncrementは常に「一番大きい番号 + 1」を作ります.これって,人気ラーメン店の行列と同じで,みんなが同じ場所(データベースの同じ部分)に殺到するんです.

結果として:

  • ロック競合が発生しやすい
  • 大量データの一括登録で性能が落ちる
  • B-treeインデックスの右端だけが「熱い」状態になる

4. 分散システムでは完全にお手上げ

マイクロサービス時代の現在,各サービスが独立してIDを生成する必要があります.でもAutoIncrementだと...

ユーザーサービス: user_id = 1, 2, 3...
商品サービス: product_id = 1, 2, 3...  ← 被ってるけど大丈夫?
注文サービス: order_id = 1, 2, 3...   ← これも被ってる!

各サービス間でのIDの一意性を保証するのが超困難になります.

5. 一番の問題:開発者がIDをコントロールできない!

これが実は最も深刻な問題かもしれません.

IDの生成タイミングが制御不能

AutoIncrementでは,IDはINSERT文が実行された瞬間にデータベースが勝手に決めます.つまり:

// こんなことができない!
product := Product{
    ID: "好きなIDを指定したい",  // ← これできない
    Name: "商品名",
}

データ移行・テスト時の悪夢

  • 本番データのID: 1, 2, 3, 4, 5...
  • テストデータのID: 1, 2, 3...

「本番のID=123の商品をテスト環境で再現したい」と思っても,AutoIncrementだと制御できません.

バックアップ・復元時の混乱

-- 本番DBのバックアップを開発環境に復元
-- でも新しいデータを追加すると...
INSERT INTO products (name) VALUES ('新商品');
-- ↑ IDが予想と違う値になることがある

APIレスポンスの一貫性問題

{
  "user_id": 12345,
  "profile_id": 67890,  // 別テーブルのAutoIncrement
  "post_id": 111        // また別のAutoIncrement
}

各IDの関係性や意味が全くわからず,デバッグ時に「このIDって何だっけ?」となりがちです.

そもそもAutoIncrementって本当に必要?

ここで根本的な疑問を投げかけてみましょう.

「AutoIncrementしたIDを使うケースって,実はあまりないのでは?」

ケース1: ユーザーID

-- AutoIncrement版
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,  -- これ必要?
    email VARCHAR(255) UNIQUE NOT NULL,
    username VARCHAR(50) UNIQUE NOT NULL
);

でも実際のところ,アプリケーションでは:

  • ログイン時はemailusernameで検索
  • プロフィール表示もusernameベース
  • API呼び出しも/users/john_doeみたいな感じのケース

XのURLも https://X.com/JavaLangRuntime みたいな感じですね.

「連番IDって,実際に使ってる?」

ケース2: 商品ID

-- AutoIncrement版
CREATE TABLE products (
    id INT AUTO_INCREMENT PRIMARY KEY,  -- これも必要?
    sku VARCHAR(50) UNIQUE NOT NULL,     -- 実際はこれを使う
    name VARCHAR(255) NOT NULL
);

実際の業務では:

  • 商品管理はsku(商品コード)ベース
  • 在庫管理もskuベース
  • URLも/products/ABC-123(SKUベース)

「またしても,連番IDの出番がない...」

ケース3: 注文ID

-- AutoIncrement版
CREATE TABLE orders (
    id INT AUTO_INCREMENT PRIMARY KEY,     -- これも?
    order_number VARCHAR(50) UNIQUE NOT NULL, -- 実際はこれ
    user_id INT NOT NULL
);

業務的には:

  • 顧客にはorder_numberを伝える(例:ORD-2024-001234)
  • 追跡や問い合わせもorder_numberベース
  • 領収書にもorder_numberが印字される

「またまた,連番IDの存在意義が...」

AutoIncrementが本当に活躍するケース

実は,AutoIncrementが本当に必要なケースって,意外と少ないんです:

1. ログテーブル

-- これは連番でもOK(むしろ楽)
CREATE TABLE access_logs (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    user_id VARCHAR(36) NOT NULL,
    accessed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    ip_address VARCHAR(45)
);

ログは「とにかく記録する」のが目的で,個別のIDに意味はありません.

2. 中間テーブル(関連テーブル)

-- ユーザーと権限の関連
CREATE TABLE user_roles (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    user_id VARCHAR(36) NOT NULL,
    role_id VARCHAR(36) NOT NULL,
    assigned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

関連性を表すだけなので,IDに特別な意味は不要です.

3. 一時的なワークテーブル

バッチ処理やデータ加工用の一時テーブルなど.

「別カラムでIDを管理」は本末転倒?

よくある「回避策」がこれです:

CREATE TABLE products (
    id INT AUTO_INCREMENT PRIMARY KEY,        -- 一応残す
    product_id VARCHAR(36) UNIQUE NOT NULL,   -- 本当のID
    name VARCHAR(255) NOT NULL
);

でも,これって:

  • ストレージ無駄遣い: 2つのインデックスが必要
  • 開発者の混乱: どっちがメインID?
  • パフォーマンス劣化: JOINが複雑になる
  • メンテナンス負荷: 2つのIDを管理する必要

現実的な解決策

-- スッキリ!
CREATE TABLE products (
    product_id VARCHAR(36) PRIMARY KEY,  -- これだけでOK
    name VARCHAR(255) NOT NULL,
    sku VARCHAR(50) UNIQUE NOT NULL
);

メリット:

  • シンプル: IDが1つだけ
  • 明確: product_idという名前で用途がわかる
  • 制御可能: 開発者がIDを決められる
  • セキュア: 推測不可能
  • 拡張性: 分散環境でも安心

代替案

「じゃあどうすればいいの?」という声が聞こえてきそうですね.安心してください,素晴らしい代替案があります!

1. UUID - 「絶対にかぶらない番号」

UUIDは128bitの超長い番号で,「実質的に絶対かぶらない」と言われています.

いいところ

  • 地球上で絶対にかぶらない: 理論上,全人類が一生懸命UUIDを作り続けても,かぶる確率は宝くじより低い
  • 分散環境でも安心: どこで作っても大丈夫
  • 推測不可能: 隣のIDを当てるのは不可能
  • 事前に作れる: データベースに保存する前にIDを決められる

ちょっと困るところ

  • 長い: 16バイトもある(AutoIncrementは4バイト)
  • 見た目が不親切: 550e8400-e29b-41d4-a716-446655440000 ← 人間には読みにくい
  • インデックス性能: ランダムすぎて,時々インデックスが頑張りすぎる
CREATE TABLE products (
    id CHAR(36) PRIMARY KEY,  -- UUIDはこんな感じ
    name VARCHAR(255) NOT NULL,
    price DECIMAL(10, 2) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

2. ULID - 「UUIDの進化版,時系列ソート対応!」

ULIDは「UUIDは良いけど,時系列順にソートできたら最高なのに...」という願いから生まれました.

すごいところ

  • 時系列でソート可能: 作成順に並べられる
  • UUIDより短い表現: 26文字で済む
  • 推測困難: セキュアです
  • 高い一意性: UUIDと同等
01ARZ3NDEKTSV4RRFFQ69G5FAV  ← こんな感じ,ちょっとスッキリ

3. Snowflake ID - 「Twitterが生み出した神システム」

Twitterが「1秒間に数万ツイートが投稿される環境で,どうやってユニークなIDを作るか」を考えて生み出した64bitのID生成システムです.

構成要素(これが天才的!)

  • タイムスタンプ (41bit): いつ作られたか
  • マシンID (10bit): どのサーバーで作られたか
  • シーケンス番号 (12bit): 同じミリ秒内での連番

素晴らしいところ

  • 超高速生成: 1秒間に数百万個作れる
  • 時系列ソート: 作成順に並ぶ
  • 64bitで効率的: AutoIncrementの倍のサイズだけど,それでも軽量
  • 分散環境完全対応: 各サーバーが独立してIDを作れる

4. カスタムIDジェネレーター - 「自分だけの特別なID」

業務要件に合わせて,独自のID生成ルールを作る方法です.

// 例:商品なら「PROD_」,ユーザーなら「USER_」で始まるID
func generateCustomID(prefix string) string {
    timestamp := time.Now().Unix()
    random := rand.Intn(999999)
    return fmt.Sprintf("%s_%d_%06d", prefix, timestamp, random)
}
// 結果: "PROD_1645123456_123456" ← 見ただけで商品IDってわかる!

実際にコードで書いてみよう

実際にGoのプロジェクトではどのようにIDを管理しているかを見てみましょう.

Go言語でUUIDを使ってみる

package main

import (
    "time"
    "github.com/google/uuid"
    "gorm.io/gorm"
)

type Product struct {
    ID        string    `gorm:"type:char(36);primaryKey" json:"id"`
    Name      string    `gorm:"not null" json:"name"`
    Price     float64   `gorm:"type:decimal(10,2);not null" json:"price"`
    CreatedAt time.Time `json:"created_at"`
}

// これでUUIDが自動生成される!
func (p *Product) BeforeCreate(tx *gorm.DB) error {
    if p.ID == "" {
        p.ID = uuid.New().String()  // 魔法の一行
    }
    return nil
}

ULIDも試してみよう

package main

import (
    "github.com/oklog/ulid/v2"
    "crypto/rand"
    "time"
)

// これでULIDが自動生成される!
func generateULID() string {
    entropy := rand.Reader  // ランダム要素
    ms := ulid.Timestamp(time.Now())  // タイムスタンプ要素
    id, _ := ulid.New(ms, entropy)
    return id.String()  // 時系列ソート可能なID完成!
}

Snowflake IDで爆速ID生成

package main

import (
    "github.com/bwmarrin/snowflake"
)

// Snowflake IDを初期化する関数
func initSnowflake(machineID int64) *snowflake.Node {
    node, err := snowflake.NewNode(machineID)
    if err != nil {
        panic(err)
    }
    return node
}

// これでSnowflake IDが自動生成される!
func generateSnowflakeID(node *snowflake.Node) int64 {
    return node.Generate().Int64()  // 超高速で一意ID生成!
}

データベース設計の違いを見てみよう

-- 従来のAutoIncrement方式(危険!)
CREATE TABLE products_old (
    id INT AUTO_INCREMENT PRIMARY KEY,  -- 推測可能で危険
    name VARCHAR(255) NOT NULL,
    price DECIMAL(10, 2) NOT NULL
);

-- UUID方式(安全だけどちょっと大きい)
CREATE TABLE products_uuid (
    id CHAR(36) PRIMARY KEY,  -- 36文字のランダム文字列
    name VARCHAR(255) NOT NULL,
    price DECIMAL(10, 2) NOT NULL
);

-- ULID方式(安全で時系列ソート可能)
CREATE TABLE products_ulid (
    id CHAR(26) PRIMARY KEY,  -- 26文字でスッキリ
    name VARCHAR(255) NOT NULL,
    price DECIMAL(10, 2) NOT NULL
);

-- Snowflake方式(高速で効率的)
CREATE TABLE products_snowflake (
    id BIGINT PRIMARY KEY,  -- 64bitの数値
    name VARCHAR(255) NOT NULL,
    price DECIMAL(10, 2) NOT NULL
);

どれを選べばいいの?性能バトル

方式 サイズ 生成速度 ソート性能 分散対応 セキュリティ 使いどころ
AutoIncrement 4 bytes 高速 最高 小規模・単一DB
UUID v4 16 bytes 普通 微妙 セキュリティ重視
ULID 16 bytes 普通 良い バランス重視
Snowflake 8 bytes 超高速 良い 高負荷対応

既存システムからの脱出作戦

「うちのシステム,すでにAutoIncrement使ってるんだけど...」という方へ.大丈夫,段階的に移行できます!

作戦1: 段階的移行

type Product struct {
    // 古いID(徐々に使わなくする)
    LegacyID int    `gorm:"column:legacy_id;autoIncrement" json:"legacy_id,omitempty"`

    // 新しいID(メインで使う)
    ID       string `gorm:"type:char(36);primaryKey" json:"id"`

    Name     string `gorm:"not null" json:"name"`
    Price    float64 `gorm:"type:decimal(10,2);not null" json:"price"`
}

作戦2: デュアルキー運用

新旧両方のIDを並行運用して,徐々に新IDに切り替える方法です.

-- 既存テーブルに新しいIDカラムを追加
ALTER TABLE products
ADD COLUMN uuid_id CHAR(36) UNIQUE;

-- 既存データにUUIDを付与
UPDATE products SET uuid_id = UUID() WHERE uuid_id IS NULL;

-- 新しいレコードは最初からUUIDを使用
-- 古いレコードへのアクセスは段階的にUUID経由に変更

結局どれを選べばいいの?

最後に,「結局うちのプロジェクトには何が良いの?」という疑問にお答えしましょう.

📱 小規模なWebアプリ・プロトタイプ

→ ULID がおすすめ

  • 将来の拡張に備えつつ,シンプルに始められる
  • 時系列ソートできるから便利

🚀 スタートアップ・成長期のサービス

→ UUID がおすすめ

  • セキュリティ重視
  • いつ分散環境に移行してもOK
  • 投資家に「セキュリティ意識が高い」とアピールできる(笑)

⚡ 高負荷・大規模サービス

→ Snowflake ID がおすすめ

  • パフォーマンス重視
  • Twitter規模でも実績あり
  • エンジニアから「おお,本格的だな」と思われる

🏢 エンタープライズ・業務システム

→ カスタムID がおすすめ

  • 業務ルールに合わせられる
  • 見ただけで何のデータかわかる
  • 運用担当者に優しい

AutoIncrementは確かに便利なのですが近年のWebサービスではさまざまな点で危険なポイントがあります.

危険な理由

  • 🔓 セキュリティリスク: 情報がダダ漏れ
  • 📈 スケーラビリティの限界: 分散環境で破綻
  • 🐌 パフォーマンス問題: 高負荷時にボトルネック

現代的な解決策

  1. UUID: セキュリティ最優先
  2. ULID: バランス重視
  3. Snowflake ID: パフォーマンス重視
  4. カスタムID: 業務要件重視

特に新規開発の場合は,「最初からAutoIncrementを使わない」ことを強くおすすめします.後から変更するより,最初から適切な方式を選んでおく方が絶対に楽ですからね.

みなさんも,次のプロジェクトでは「脱AutoIncrement」を検討してみてください.きっと,より安全で拡張性の高いシステムを構築できるはずです!

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?