📌

DBのインデックスを完全理解しよう!📚

に公開

DBのインデックスを完全理解しよう!📚

データベースのインデックスって聞いたことはあるけど、実際どういうものかよくわからない...そんな方も多いのではないでしょうか?

この記事では、本の索引という身近な例から始めて、データベースのインデックスを完全に理解できるよう解説します。

📖 本の索引で理解するインデックスの仕組み

索引なしの本で特定の言葉を探すと...

想像してみてください。500ページの分厚い技術書で「データベース」という言葉が出てくるページを全て見つけたいとします。

索引がない場合:

1ページ目: 「データベース」はあるかな? → なし
2ページ目: 「データベース」はあるかな? → なし
3ページ目: 「データベース」はあるかな? → なし
...
500ページ目まで全てチェック!

これは**全表スキャン(Full Table Scan)**と呼ばれる状況です。最悪の場合、500ページ全てを確認する必要があります。

索引がある本なら...

索引を開く
「た」の項目 → 「データベース」を発見!
→ 「15ページ、89ページ、234ページ、456ページ」

たった数秒で完了!

🏪 実店舗での例え

コンビニの商品陳列(インデックスあり)

  • ドリンクコーナー: 冷たい飲み物はここ
  • パンコーナー: パンはここ
  • お菓子コーナー: お菓子はここ

欲しい商品をすぐに見つけられます!

倉庫のランダム配置(インデックスなし)

商品がランダムに置かれている倉庫では、コーラを見つけるために全ての棚を端から順番に探す必要があります...

💾 データベースでのインデックス

ユーザーテーブルの例

users テーブル(100万レコード)
id | name     | email
1  | 田中太郎  | tanaka@example.com
2  | 佐藤花子  | sato@example.com
3  | 鈴木一郎  | suzuki@example.com
...
999999 | 山田次郎 | yamada@example.com

インデックスなしでの検索

SELECT * FROM users WHERE email = 'yamada@example.com';

データベースの動作:

レコード1: tanaka@example.com == yamada@example.com? → NO
レコード2: sato@example.com == yamada@example.com? → NO
レコード3: suzuki@example.com == yamada@example.com? → NO
...
レコード999999: yamada@example.com == yamada@example.com? → YES!

結果: 100万レコード全てをチェック!⏰

インデックスありでの検索

emailカラムにインデックスを作成すると、データベース内部で索引表が作られます。

email索引表(アルファベット順にソート済み)
sato@example.com    → レコード2
suzuki@example.com  → レコード3  
tanaka@example.com  → レコード1
yamada@example.com  → レコード999999

検索時の動作:

1. 索引表をバイナリサーチ(二分探索)で検索
2. yamada@example.com を発見
3. 対応するレコード999999を直接取得

結果: 数回の比較で完了!⚡

🚀 Railsでのインデックス実装

1. マイグレーション生成

$ rails generate migration add_index_to_users_email

2. マイグレーションファイル編集

# db/migrate/[timestamp]_add_index_to_users_email.rb
class AddIndexToUsersEmail < ActiveRecord::Migration[7.0]
  def change
    add_index :users, :email, unique: true
  end
end

3. マイグレーション実行

$ rails db:migrate

⚡ パフォーマンスの違い

実際の数値例

レコード数 インデックスなし インデックスあり 速度向上
1,000 500回の比較 約10回の比較 50倍
100,000 50,000回の比較 約17回の比較 2,941倍
1,000,000 500,000回の比較 約20回の比較 25,000倍

🔒 一意性制約の重要性

問題のシナリオ

# 同時に2つのリクエストが処理される場合

# リクエスト1のタイミング
user1 = User.new(email: "alice@example.com")
user1.valid? # => true (まだDBには存在しない)

# リクエスト2のタイミング(ほぼ同時)
user2 = User.new(email: "alice@example.com") 
user2.valid? # => true (まだDBには存在しない)

# 保存処理
user1.save # 成功
user2.save # Active Recordレベルでは成功してしまう!

解決策: データベースレベルの一意性制約

# マイグレーション
add_index :users, :email, unique: true

これにより、データベースレベルで重複が防がれます:

user1.save # 成功
user2.save # ActiveRecord::RecordNotUnique エラーが発生

📊 インデックスの種類

1. 通常のインデックス

add_index :users, :name

検索速度は向上するが、重複は許可

2. 一意インデックス

add_index :users, :email, unique: true

検索速度向上 + 重複防止

3. 複合インデックス

add_index :users, [:last_name, :first_name]

複数カラムの組み合わせで検索する場合に有効

⚠️ インデックスの注意点

デメリットもあります

  1. 追加のストレージ容量: 索引表の分だけ容量が増加
  2. 書き込み速度の低下: データ挿入・更新時に索引表も更新が必要
  3. メンテナンス: インデックスの管理が必要

いつインデックスを作るべき?

作るべき場合:

  • 頻繁に検索されるカラム
  • WHERE句でよく使われるカラム
  • 一意性を保証したいカラム
  • 外部キー

不要な場合:

  • ほとんど検索されないカラム
  • 頻繁に更新されるテーブルの多数のカラム

🛠️ 実践的なマイグレーション例

class AddIndexesToUsers < ActiveRecord::Migration[7.0]
  def change
    # 一意性を保証(ユーザー登録用)
    add_index :users, :email, unique: true
    
    # 検索パフォーマンス向上(ログイン用)
    add_index :users, :username
    
    # 複合インデックス(名前での検索用)
    add_index :users, [:last_name, :first_name]
    
    # 外部キー用
    add_index :posts, :user_id
  end
end

🔍 インデックスの確認方法

Rails console

# インデックス一覧を表示
ActiveRecord::Base.connection.indexes('users')

データベース直接確認(PostgreSQL)

\d users

データベース直接確認(MySQL)

SHOW INDEX FROM users;

📈 実際の効果測定

ベンチマークの例

# インデックス追加前
Benchmark.measure do
  1000.times { User.find_by(email: "test#{rand(100000)}@example.com") }
end
# => 2.5秒

# インデックス追加後
Benchmark.measure do
  1000.times { User.find_by(email: "test#{rand(100000)}@example.com") }
end  
# => 0.05秒(50倍高速化!)

🎯 まとめ

データベースのインデックスは:

  1. 本の索引と同じ仕組みで動作
  2. 検索速度を劇的に向上させる
  3. 一意性制約でデータ整合性を保証
  4. 適切に使えばアプリケーションのパフォーマンスが大幅改善

特にRailsアプリケーションでは、ユーザー認証機能でemailカラムにuniqueインデックスを追加することは必須と言えるでしょう。

まずは自分のアプリケーションで頻繁に検索するカラムから、インデックスを追加してみてください!


参考資料

  • Rails Guide: Active Record Migrations
  • データベース設計のベストプラクティス

Discussion