MongoDB
 Computer >> コンピューター >  >> プログラミング >> MongoDB

MongoDBのexplain()でクエリプランを読み解く:db.collection.find()と正規表検索の実践解説

MongoDBでは、explain()メソッドを使用することで、クエリがどのように実行されるかを示すクエリプランの詳細情報を確認できます。本記事では、サンプルコレクションを作成し、正規表現を使ったfind()クエリの実行プランを2つのケースに分けて解説します。

準備:コレクションの作成とドキュメントの挿入

まず、ClientNameフィールドにインデックスを作成したうえで、テスト用コレクションdemo637にドキュメントを挿入します。

> db.demo637.ensureIndex({ClientName:1});
{
    "createdCollectionAutomatically" : true,
    "numIndexesBefore" : 1,
    "numIndexesAfter" : 2,
    "ok" : 1
}
> db.demo637.insert({ClientName:"John"});
WriteResult({ "nInserted" : 1 })
> db.demo637.insert({ClientName:"Bob"});
WriteResult({ "nInserted" : 1 })
> db.demo637.insert({ClientName:"Johnson"});
WriteResult({ "nInserted" : 1 })

find()メソッドですべてのドキュメントを表示して確認しましょう。

> db.demo637.find();

実行結果は以下のとおりです。

{ "_id" : ObjectId("5e9c26916c954c74be91e6db"), "ClientName" : "John" }
{ "_id" : ObjectId("5e9c26936c954c74be91e6dc"), "ClientName" : "Bob" }
{ "_id" : ObjectId("5e9c269d6c954c74be91e6dd"), "ClientName" : "Johnson" }

ケース1:部分一致の正規表現「/john/」でクエリを実行

まずは、文字列の途中にもマッチする正規表現/john/でクエリを実行し、explain()でプランを確認します。

> db.demo637.find({ClientName:/john/}).explain();

出力結果は以下のとおりです。

{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "test.demo637",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "ClientName" : {
                "$regex" : "john"
            }
        },
        "winningPlan" : {
            "stage" : "FETCH",
            "inputStage" : {
                "stage" : "IXSCAN",
                "filter" : {
                    "ClientName" : {
                        "$regex" : "john"
                    }
                },
                "keyPattern" : {
                    "ClientName" : 1
                },
                "indexName" : "ClientName_1",
                "isMultiKey" : false,
                "multiKeyPaths" : {
                    "ClientName" : [ ]
                },
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 2,
                "direction" : "forward",
                "indexBounds" : {
                    "ClientName" : [
                        "[\"\", {})",
                        "[/john/, /john/]"
                    ]
                }
            }
        },
        "rejectedPlans" : [ ]
    },
    "serverInfo" : {
        "host" : "DESKTOP-QN2RB3H",
        "port" : 27017,
        "version" : "4.0.5",
        "gitVersion" : "3739429dd92b92d1b0ab120911a23d50bf03c412"
    },
    "ok" : 1
}

このケースでは、winningPlanとしてFETCHステージとその内部でIXSCAN(インデックススキャン)が選択されています。ただし、indexBoundsを見ると範囲が["", {})となっており、インデックス全体を対象とした広い走査になっている点に注目してください。前方一致ではないため、インデックスによる絞り込み効果は限定的です。

ケース2:前方一致の正規表現「/^john/」でクエリを実行

次に、先頭固定(アンカー付き)の正規表現/^john/でクエリを実行します。

> db.demo637.find({ClientName:/^john/}).explain();

出力結果は以下のとおりです。

{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "test.demo637",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "ClientName" : {
                "$regex" : "^john"
            }
        },
        "winningPlan" : {
            "stage" : "FETCH",
            "inputStage" : {
                "stage" : "IXSCAN",
                "keyPattern" : {
                    "ClientName" : 1
                },
                "indexName" : "ClientName_1",
                "isMultiKey" : false,
                "multiKeyPaths" : {
                    "ClientName" : [ ]
                },
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 2,
                "direction" : "forward",
                "indexBounds" : {
                    "ClientName" : [
                        "[\"john\", \"joho\")",
                        "[/^john/, /^john/]"
                    ]
                }
            }
        },
        "rejectedPlans" : [ ]
    },
    "serverInfo" : {
        "host" : "DESKTOP-QN2RB3H",
        "port" : 27017,
        "version" : "4.0.5",
        "gitVersion" : "3739429dd92b92d1b0ab120911a23d50bf03c412"
    },
    "ok" : 1
}

両ケースの違いとパフォーマンス上のポイント

ケース2の出力で注目すべきはindexBoundsの内容です。["john", "joho")という範囲が指定されており、MongoDBが「john」から「joho」直前までのインデックス範囲だけを効率的に走査していることがわかります。これは、正規表現が^で先頭固定されているため、Bツリーインデックスの順序性を利用した範囲絞り込み(prefix bounds)が可能になったからです。

一方、ケース1のような中間一致の正規表現では範囲を特定できないため、インデックススキャン自体は行われるものの、実質的にインデックス全体を確認することになります。大規模なコレクションで正規表現検索を行う場合は、可能な限り^による前方一致を活用することで、クエリパフォーマンスを大幅に改善できます。

このように、explain()の出力にあるqueryPlannerwinningPlanindexBoundsなどの項目を読み解くことで、クエリがインデックスをどれほど有効に活用しているかを正確に把握できます。クエリチューニングの第一歩として、ぜひ活用してみてください。

  1. MongoDBのfind()クエリでネストされたドキュメントを取得する方法

    MongoDBでネストされたドキュメント(入れ子になったサブドキュメントや配列内のオブジェクト)から値を取得するには、「ドット表記(dot notation)」を使用します。この記事では、サンプルコレクションを作成し、find()クエリを使ってネストされたデータを検索する具体的な手順を解説します。 サンプルコレクションの作成 まず、insert()メソッドを使ってドキュメントを含むコレクションを作成しましょう。 > db.demo591.insert([ ... { "Name": "John", "Age": 23 },

  2. MongoDBでフィールド値に含まれる「@email」のような文字列を検索する方法

    MongoDBのfind()メソッドを使えば、正規表現(Regex)を活用して、フィールド値の中から特定の文字列を柔軟に検索できます。本記事では、「@email」という文字列を含むドキュメントを検索する方法を、実際のコード例とともに解説します。1. 検索用コレクションの作成まずは、メールアドレスを格納したドキュメントを持つコレクションを作成しましょう。> db.demo727.insertOne({UserId:John@email.com}); { acknowledged : true, insertedId : ObjectId(5eab375f4341781127