【MongoDB】MongoDBのインデックスの基本とパフォーマンス改善の考え方

MongoDB

こんにちは、かつコーチです。

「検索は動くけど、データが増えたら急に遅くなった」という相談を、MongoDBを使い始めた方からよく受けます。

原因のほとんどはインデックス(検索を高速化するためのデータ構造。SQLのB-Treeインデックスと似た役割を持つ)が張られていないことです。

RDBMSのインデックスと考え方は似ていますが、MongoDBならではの複合インデックスの張り方や確認方法があるので、この記事で基礎から実践までまとめます。

MongoDB 7.x系のmongoshを前提に進めます。

インデックスとは?

インデックスがないと何が起きるのか

インデックスがない状態でクエリを実行すると、MongoDBはコレクションスキャンCOLLSCAN。全ドキュメントを1件ずつ確認する処理)を行います。

件数が少ないうちは気づきませんが、数万件を超えたあたりから検索速度が目に見えて落ちてきます。

// 実行計画を確認する(explainメソッド)
db.users.find({ email: "taro@example.com" }).explain("executionStats");

explain()の結果に"stage": "COLLSCAN"と表示されていれば、インデックスが使われずに全件走査していることが分かります。

なぜインデックスが必要なのか

インデックスは、指定したフィールドの値をあらかじめソートした構造で保持しておくことで、目的のドキュメントを素早く見つけられるようにする仕組みです。

本を「目次」を使わずに最初から1ページずつめくって探すのか、目次から該当ページに直接飛ぶのかの違いに近いです。

一方で、インデックスは書き込み(insertupdate)のたびに更新コストが発生するため、「検索を速くする代わりに書き込みが少し遅くなる」というトレードオフがある点も押さえておく必要があります。

インデックスの作成と確認

単一フィールドインデックス

もっとも基本的なインデックスは、1つのフィールドに対して作成します。

// emailフィールドに昇順のインデックスを作成する
db.users.createIndex({ email: 1 });

// -1を指定すると降順インデックスになる
db.users.createIndex({ createdAt: -1 });

作成済みのインデックス一覧はgetIndexes()で確認できます。

db.users.getIndexes();

複合インデックス

複数条件での検索が多い場合は、複合インデックス(複数フィールドを組み合わせたインデックス)が有効です。

// statusで絞り込み、createdAtで並び替えるクエリに対応する複合インデックス
db.orders.createIndex({ status: 1, createdAt: -1 });

複合インデックスにはESRルール(Equality→Sort→Range、等価条件→ソート→範囲条件の順にフィールドを並べるという設計原則)があり、フィールドの順序を意識しないと期待した効果が得られません。

// statusは等価条件、createdAtはソート条件なので、この順序が正しい
db.orders.find({ status: "shipped" }).sort({ createdAt: -1 });

よくあるつまずきポイント・エラー対処

インデックスを張ったのに使われない

筆者が実際に経験したケースですが、複合インデックスを作成したのにexplain()で確認するとCOLLSCANのままだったことがありました。

原因は、クエリの絞り込み条件の順序とインデックスのフィールド順序が噛み合っていなかったことでした。

❌ Before(インデックスのフィールド順序とクエリの意図がずれている)

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

// クエリ: statusの等価条件が先頭に来ていないため効率が悪い
db.orders.find({ status: "shipped" }).sort({ createdAt: -1 });

✅ After(ESRルールに沿って等価条件のフィールドを先頭にする)

// インデックス: { status: 1, createdAt: -1 } の順に作り直す
db.orders.createIndex({ status: 1, createdAt: -1 });

db.orders.find({ status: "shipped" }).sort({ createdAt: -1 });

インデックスを作り直した後、explain("executionStats")"stage""IXSCAN"(インデックススキャン)に変わり、totalDocsExamined(実際に走査したドキュメント数)が数万件から数十件まで減ったことを確認できました。

複合インデックスは「作れば速くなる」ものではなく、「クエリの絞り込み方に合わせて設計するもの」という意識が重要です。

応用・一歩先の使い方

不要なインデックスの棚卸し

インデックスは書き込み性能を下げる要因にもなるため、使われていないインデックスは定期的に見直すべきです。

// $indexStatsで各インデックスの使用状況を確認する
db.orders.aggregate([{ $indexStats: {} }]);

このステージを使うと、インデックスごとの利用回数(accesses.ops)が確認でき、長期間0のままのインデックスは削除を検討する材料になります。

// 不要なインデックスの削除
db.orders.dropIndex({ oldField: 1 });

まとめ

この記事のポイント

  • インデックスがないとコレクションスキャン(COLLSCAN)が発生し、データ量に比例して検索が遅くなる
  • 複合インデックスはESRルール(等価→ソート→範囲)に沿ってフィールド順序を設計する
  • explain("executionStats")IXSCANになっているか、totalDocsExaminedが想定通りかを必ず確認する
  • $indexStatsで使われていないインデックスを棚卸しし、書き込み性能への影響を抑える

次に読むべき記事

  • 集約パイプライン(Aggregation)の基本(MongoDB編6本目)
  • スキーマ設計の考え方:埋め込みvs参照(MongoDB編8本目)
  • MongoDBとPostgreSQL(JSONB)比較検証(MongoDB編最終回)

タグ: MongoDB, 中級者向け, パフォーマンス

タイトルとURLをコピーしました