MongoDB: インデックスの基礎と原則

最終更新:2026-08-26

インデックスはデータベースパフォーマンスの鍵です—インデックスの原則と最適化を習得すれば、クエリ速度を1,000倍に向上できます。

1. 学習内容



2. インデックスの原理(Bツリー)

概念説明: インデックスは補助的なデータ構造で、本の目次のように、データベースがコレクション全体をスキャンせずにドキュメントを迅速に特定できるようにします。MongoDBはBツリーベースのインデックス構造(WiredTigerエンジンはB+ツリーのバリアントを使用)を使用し、フィールド値とドキュメント位置の順序付きマッピングを確立し、クエリの時間計算量をO(N)からO(log N)に削減します。

動作原理: MongoDBのB+ツリーインデックスは、内部ノードにフィールド値をルーティング用に保存し、リーフノードに実際のキー値とドキュメントポインタを保存します。リーフノードは双方向連結リストで接続されており、範囲クエリとソートを自然にサポートします。クエリがインデックスにマッチすると、エンジンはルートノードから階層ごとにキー値を比較し、最終的にリーフノードでターゲットドキュメントの物理位置を特定し、全コレクションスキャン(COLLSCAN)を回避します。

使用用途:

100%
graph TB
    A[ルートノード<br/>50-100] --> B[内部ノード1<br/>20-50]
    A --> C[内部ノード2<br/>20-50]
    B --> D[リーフノード1<br/>ドキュメントへリンク]
    B --> E[リーフノード2]
    C --> F[リーフノード3]
    C --> G[リーフノード4]
    D <--> E <--> F <--> G

    style A fill:#cce5ff
    style D fill:#d4edda
    style E fill:#d4edda
    style F fill:#d4edda
    style G fill:#d4edda

B+ツリーのクエリプロセス: 等価クエリの場合、ルートノードから階層ごとに比較→リーフノードに到達→ドキュメントポインタを返す;範囲クエリの場合、開始リーフノードを特定した後、連結リストを順次スキャン→条件を満たす全ドキュメントを収集。

100%
sequenceDiagram
    participant App as アプリケーションクエリ
    participant WT as WiredTigerエンジン
    participant IX as B+ツリーインデックス
    participant DOC as ドキュメントコレクション

    App->>WT: find({sku: 'SKU-005'})
    WT->>IX: ルートノード比較 50<005<100 → 左サブツリー
    IX-->>WT: 内部ノード: 20<005<50 → 左葉
    WT->>IX: リーフノード検索 SKU-005
    IX-->>WT: 発見 → doc_pointer=0x7F3A
    WT->>DOC: 0x7F3A位置のドキュメントを読み取り
    DOC-->>App: マッチしたドキュメントを返す

    note over WT,IX: 時間計算量 O(log N)<br/>全ドキュメントをスキャン不要
操作 全件スキャン(COLLSCAN) B+ツリーインデックス(IXSCAN) パフォーマンス差
等価クエリ O(N) O(log N) 100,000行: 100K vs 17
範囲クエリ O(N) O(log N + K) 100,000行: 100K vs 17+K
ソート O(N log N) O(log N + K) インデックスはソート済み、ソート不要
挿入/更新 O(1) O(log N) インデックス維持の追加オーバーヘッド

(1) WiredTigerインデックス保存の詳細

次元 説明
インデックス形式 B+ツリー、キー・バリューペアをソート済みで保存
リーフノード インデックスキー + RecordID(ドキュメント位置ポインタ)を含む
内部ノード ルートキーと子ノードへのポインタのみを含む
連結リスト接続 リーフノードが双方向連結リストを形成、順次スキャンをサポート
圧縮 プレフィックス圧縮でストレージを削減


3. createIndexの構文

概念説明: createIndex()はMongoDBでインデックスを作成する中核コマンドです—指定フィールドのB+ツリーインデックス構造を構築するようエンジンに指示します。インデックス作成後、クエリオプティマイザは自動的に使用するかどうかを判断—開発者がクエリ文を修正する必要はありません。

動作原理: インデックス作成時、MongoDBはコレクション内の全ドキュメントをスキャンし、インデックス対象フィールドの値を抽出、ソート、B+ツリー構造を構築してディスクに書き込みます。このプロセス中、コレクションに書き込みロックが保持されます(インデックスはバックグラウンドで構築可能 background: true)、大規模コレクションでのインデックス作成は数分から数時間かかる可能性があります。

構文ルール:

パラメータ 説明
keys object インデックスフィールドと方向:1昇順、-1降順
unique boolean 一意インデックスかどうか、デフォルトfalse
background boolean バックグラウンドで構築するか(読み書き操作をブロックしない)、デフォルトfalse
name string カスタムインデックス名、デフォルトfield_1
partialFilterExpression object 部分インデックスの条件(条件を満たすドキュメントのみインデックス化)
sparse boolean スパースインデックス、nullフィールドをスキップ
expireAfterSeconds number TTLインデックス、自動期限切れ削除(秒)
v number インデックスバージョン、デフォルトv=2
JAVASCRIPT
// === 単一フィールドインデックス作成 ===
db.products.createIndex({ sku: 1 });          // 昇順
db.products.createIndex({ createdAt: -1 });   // 降順

// === 複合インデックス作成 ===
db.products.createIndex({ category: 1, price: -1 });

// === 一意インデックス作成 ===
db.products.createIndex({ sku: 1 }, { unique: true });

// === バックグラウンドで作成(非ブロッキング)===
db.products.createIndex({ tags: 1 }, { background: true });

// === カスタムインデックス名 ===
db.products.createIndex({ title: 1 }, { name: 'idx_title' });

要点分析:

  1. 1と-1はインデックスのソート順に影響、単一フィールドインデックスでは実質的な影響はないが、複合インデックスでのソート最適化には重要
  2. background: trueはMongoDB 4.2以降、デフォルトでバックグラウンド構築、パラメータは保持されるが明示的に指定不要
  3. 各コレクションには_idというデフォルトの一意インデックスがある(削除不可)、追加作成不要

▶ サンプル 1:createIndexとインデックス作成の監視

JAVASCRIPT
// ShopHub EC:商品コレクションに主要インデックスを作成、構築進捗を監視
db.products.createIndex({ category: 1, price: -1, rating: -1 }, { name: 'idx_category_price_rating', background: true });

// インデックス構築進捗を確認
db.currentOp({
  $or: [
    { op: 'command', 'command.createIndexes': { $exists: true } },
    { op: 'none', ns: /shopdb\.products/ }
  ]
});

// 全インデックスを確認
db.products.getIndexes();

出力:

TEXT 📖 参照専用
[
  { v: 2, key: { _id: 1 }, name: '_id_' },
  { v: 2, key: { category: 1, price: -1, rating: -1 }, name: 'idx_category_price_rating' }
]


4. explain()実行プラン

概念説明: explain()はMongoDBのクエリ分析ツールで、クエリオプティマイザが選択した実行プランを返し、インデックスが使用されたか、何件のドキュメントがスキャンされたか、操作時間など重要な指標を確認できます。インデックス最適化の「レントゲン」—まずexplain()を実行してから最適化。

動作原理: MongoDBクエリオプティマイザは各クエリに対して複数の候補プランを生成、実行、パフォーマンスを比較、最適なプランをキャッシュします。explain()は3つのレベルを出力:

100%
graph LR
    A[クエリリクエスト] --> B[クエリオプティマイザ]
    B --> C[候補プラン生成]
    C --> D[プランA: IXSCAN]
    C --> E[プランB: COLLSCAN]
    D --> F[実行比較]
    E --> F
    F --> G[最適プラン選択]
    G --> H[キャッシュ + 実行]

    style G fill:#d4edda
    style E fill:#f8d7da

使用用途:

実行プランの読み方:

  1. winningPlan.stageCOLLSCANならインデックス未使用
  2. winningPlan.stageIXSCANならインデックス使用
  3. totalDocsExaminednReturnedより大幅に大きいと非効率

▶ サンプル 2:explain()でクエリを分析

JAVASCRIPT
// インデックスなしでクエリ
db.products.find({ category: 'Electronics', price: { $gte: 100 } }).explain('executionStats');

// インデックス作成後
db.products.createIndex({ category: 1, price: 1 });
db.products.find({ category: 'Electronics', price: { $gte: 100 } }).explain('executionStats');

出力:

TEXT 📖 参照専用
// インデックスなし:COLLSCAN(全件スキャン)
winningPlan.stage: COLLSCAN
totalDocsExamined: 10000

// インデックスあり:IXSCAN(インデックススキャン)
winningPlan.stage: IXSCAN
totalDocsExamined: 150

重要な指標の解釈:

指標 説明 目標値
winningPlan.stage 実行ステージ IXSCAN(COLLSCANを避ける)
totalDocsExamined スキャンされたドキュメント数 nReturnedに近い
nReturned 返されたドキュメント数 クエリ結果数
executionTimeMillis 実行時間(ミリ秒) できるだけ低く


5. 複合インデックス

概念説明: 複合インデックスは複数のフィールドで構成されるインデックスで、複数条件のクエリを最適化します。フィールドの順序が重要—クエリ条件がインデックスのプレフィックスにマッチする必要があります。

動作原理: 複合インデックス{category: 1, price: -1}は、まずcategoryでソートし、同じcategory内でpriceの降順でソートします。クエリがcategoryのみの条件でもインデックスを使用可能、priceのみの条件では使用不可(プレフィックス原則)。

フィールド順序の設計原則:

  1. 等価条件フィールドを先にcategory: 'Electronics'など)
  2. 範囲条件フィールドを後にprice: {$gte: 100}など)
  3. ソートフィールドを考慮(ソート順序とインデックス順序が一致すれば効率的)
100%
graph LR
    A[複合インデックス<br/>{category: 1, price: -1}] --> B[クエリ1: {category: 'A'}]
    A --> C[クエリ2: {category: 'A', price: {$gte: 100}}]
    A --> D[クエリ3: {price: {$gte: 100}}]

    B --> E[✅ インデックス使用]
    C --> F[✅ インデックス使用]
    D --> G[❌ インデックス不使用<br/>(プレフィックス不一致)]

    style E fill:#d4edda
    style F fill:#d4edda
    style G fill:#f8d7da

複合インデックスの選択性: 選択性はインデックスがどれだけドキュメントを絞り込めるかを示します:

▶ サンプル 3:複合インデックスの効果

JAVASCRIPT
// サンプルデータ
db.orders.insertMany([
  { orderNumber: 'ORD-001', userId: ObjectId(), status: 'paid', total: 599, createdAt: new Date('2026-07-01') },
  { orderNumber: 'ORD-002', userId: ObjectId(), status: 'pending', total: 299, createdAt: new Date('2026-07-02') },
  { orderNumber: 'ORD-003', userId: ObjectId(), status: 'paid', total: 899, createdAt: new Date('2026-07-03') }
]);

// 複合インデックス作成
db.orders.createIndex({ status: 1, createdAt: -1 });

// クエリ1:プレフィックス条件のみ
db.orders.find({ status: 'paid' }).explain();
// IXSCAN使用

// クエリ2:複合条件
db.orders.find({ status: 'paid', createdAt: { $gte: new Date('2026-07-02') } }).explain();
// IXSCAN使用

// クエリ3:プレフィックスなし
db.orders.find({ createdAt: { $gte: new Date('2026-07-02') } }).explain();
// COLLSCAN(インデックス不使用)


6. カバークエリ(Covered Query)

概念説明: カバークエリとは、クエリ条件と返却フィールドがすべてインデックスで満たされるクエリです—ドキュメント本体を参照する必要がなく、インデックスのみで完結するため非常に高速です。

動作原理: カバークエリでは、MongoDBはインデックスエントリから必要なフィールドを直接読み取り、ドキュメントをフェッチしません。これによりディスクI/Oが削減され、パフォーマンスが大幅に向上します。

カバークエリの条件:

  1. クエリ条件がすべてインデックスに含まれる
  2. 返却フィールドがすべてインデックスに含まれる
  3. 結果配列などインデックス外のフィールドを返さない
JAVASCRIPT
// インデックス作成
db.products.createIndex({ sku: 1, title: 1, price: 1 });

// カバークエリ:_idを除外し、インデックスフィールドのみ返す
db.products.find(
  { sku: 'PHONE-001' },
  { _id: 0, sku: 1, title: 1, price: 1 }
).explain('executionStats');
// totalDocsExamined: 0(ドキュメントをフェッチしない)

出力:

TEXT 📖 参照専用
winningPlan.stage: PROJECTION
totalDocsExamined: 0  // ドキュメントフェッチなし
totalKeysExamined: 1  // インデックスキーのみ

カバークエリの判断: explain()totalDocsExamined0であればカバークエリです。



7. インデックスのコストとトレードオフ

概念説明: インデックスは読み取りを高速化しますが、書き込みを遅くし、ストレージを消費します。適切なバランスが必要です。

インデックスのコスト:

コスト項目 説明
書き込みオーバーヘッド 各挿入/更新/削除でインデックスも更新が必要
ストレージ消費 インデックスはデータの約10–30%の追加ストレージを消費
メモリ使用 インデックスはメモリにキャッシュされることが望ましい
作成時間 大規模コレクションでは構築に時間がかかる

インデックス設計のベストプラクティス:

推奨 説明
必要なインデックスのみ 過剰なインデックスは書き込みパフォーマンスを低下
ESRルールに従う Equality → Sort → Rangeの順序でフィールド配置
定期的なレビュー 使用されていないインデックスを削除
explain()で検証 実際に使用されているか確認

インデックスメンテナンスコマンド:

JAVASCRIPT
// 未使用インデックスの確認
db.products.aggregate([
  { $indexStats: {} },
  { $match: { "accesses.ops": 0 } }
]);

// インデックス削除
db.products.dropIndex('idx_title');

// インデックス情報
db.products.getIndexes();


8. 総合実践トレーニング

概念概要: この総合演習では、ECシステムの注文コレクションを例に、インデックス設計の実践的なアプローチを学びます。クエリパターンを分析し、最適なインデックス戦略を設計します。

ShopHub注文システムのインデックス設計:

JAVASCRIPT
// よくあるクエリパターン:
// 1. ユーザー別注文検索
// 2. ステータス別注文検索(日付順)
// 3. 価格範囲でフィルタリング

// 推奨インデックス設計
db.orders.createIndex({ userId: 1 });                                    // ユーザー別クエリ
db.orders.createIndex({ status: 1, createdAt: -1 });                    // ステータス+日付
db.orders.createIndex({ status: 1, total: 1 });                         // ステータス+価格範囲

// 複合インデックス(複数条件)
db.orders.createIndex({ userId: 1, status: 1, createdAt: -1 });

// 全インデックス確認
db.orders.getIndexes();

▶ サンプル 4:注文システムのインデックス最適化

JAVASCRIPT
// サンプル注文データ
db.orders.insertMany([
  { orderNumber: 'ORD-001', userId: ObjectId('507f1f77bcf86cd799439011'), status: 'paid', total: 599, createdAt: new Date('2026-07-01') },
  { orderNumber: 'ORD-002', userId: ObjectId('507f1f77bcf86cd799439011'), status: 'shipped', total: 299, createdAt: new Date('2026-07-02') },
  { orderNumber: 'ORD-003', userId: ObjectId('507f1f77bcf86cd799439012'), status: 'paid', total: 899, createdAt: new Date('2026-07-03') }
]);

// インデックス作成
db.orders.createIndex({ userId: 1, status: 1, createdAt: -1 });

// クエリ実行と検証
db.orders.find({
  userId: ObjectId('507f1f77bcf86cd799439011'),
  status: 'paid'
}).sort({ createdAt: -1 }).explain('executionStats');

出力:

TEXT 📖 参照専用
winningPlan.stage: FETCH
winningPlan.inputStage.stage: IXSCAN
totalDocsExamined: 1
executionTimeMillis: 2

❓ よくある質問

インデックス使用時によくある落とし穴: 1. 過剰なインデックス—書き込みパフォーマンスを低下;2. 間違ったフィールド順序—複合インデックスが使用されない;3. $orの誤用—各条件が別々にインデックス使用、組み合わせは不可;4. 正規表現の前方一致以外—/^pattern/以外はインデックス不使用。

Q いくつインデックスを作成すべき?
A クエリパターンに基づき、通常コレクションあたり3–5個が目安。過剰インデックスは書き込みを遅くする。
Q 複合インデックスのフィールド順序はどう決める?
A ESRルール:Equality(等価条件)→ Sort(ソート)→ Range(範囲条件)の順序。
Q インデックスが使用されているかどう確認?
A explain('executionStats')winningPlan.stageを確認。IXSCANなら使用、COLLSCANなら不使用。
Q 大文字小文字を区別しない検索でインデックスを使うには?
A 正規表現の/^pattern/iはインデックス使用不可。小文字で保存し、クエリも小文字で検索するのが推奨。

📖 まとめ

知識ネットワーク: インデックスはデータベースパフォーマンス最適化の基盤です—正しく設計されたインデックスはクエリを数秒から数ミリ秒に高速化できます。explain()で検証、ESRルールで設計、定期的にレビューすることが重要。


📝 練習問題

  1. 基本問題(⭐):商品コレクションにskuフィールドの一意インデックスを作成。
  2. 基本問題(⭐)explain()でインデックスが使用されているか確認。
  3. 応用問題(⭐⭐):注文コレクションに複合インデックス{userId: 1, status: 1, createdAt: -1}を作成し、クエリパターンを検証。
  4. 応用問題(⭐⭐):カバークエリを作成し、totalDocsExamined: 0を確認。
  5. チャレンジ問題(⭐⭐⭐):未使用インデックスを特定し削除、パフォーマンス改善を測定。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%