Help us understand the problem. What is going on with this article?

MongoDB で hint() を使って存在しないインデックスを指定すると

エラーを吐く。

hint()

MongoDB でクエリを発行する際に「このインデックスを使ってほしい」という状況では hint() を使ってインデックスを指定することができる。

前提として以下のようなインデックスが設定されているとする。

> db.items.getIndexes()
[
        {
                "v" : 2,
                "key" : {
                        "_id" : 1
                },
                "name" : "_id_",
                "ns" : "sandbox.items"
        },
        {
                "v" : 2,
                "key" : {
                        "memo" : 1
                },
                "name" : "memo_1",
                "ns" : "sandbox.items"
        }
]

以下のようなクエリを書けば items に対して検索を行う際に使うインデックスを指定することができる。

> db.items.find().hint({memo: 1})
{ "_id" : ObjectId("5dea0c12645c1b6ec8cc356f"), "name" : "item3", "memo" : null }
{ "_id" : ObjectId("5dea0c19645c1b6ec8cc3570"), "name" : "item4" }
{ "_id" : ObjectId("5dea0c0d645c1b6ec8cc356e"), "name" : "item2", "memo" : "" }
{ "_id" : ObjectId("5dea0c07645c1b6ec8cc356d"), "name" : "item1", "memo" : "hoge" }

実際に意図するインデックスが使われているかどうかは explain で確認できる。

インデックスを指定しない場合

> db.items.find().explain()
{
        "queryPlanner" : {
                "plannerVersion" : 1,
                "namespace" : "sandbox.items",
                "indexFilterSet" : false,
                "parsedQuery" : {

                },
                "queryHash" : "8B3D4AB8",
                "planCacheKey" : "8B3D4AB8",
                "winningPlan" : {
                        "stage" : "COLLSCAN",
                        "direction" : "forward"
                },
                "rejectedPlans" : [ ]
        },
        "serverInfo" : {
                "host" : "aa8a63a66c06",
                "port" : 27017,
                "version" : "4.2.1",
                "gitVersion" : "edf6d45851c0b9ee15548f0f847df141764a317e"
        },
        "ok" : 1
}

インデックスを指定した場合

> db.items.find().hint({memo: 1}).explain()
{
        "queryPlanner" : {
                "plannerVersion" : 1,
                "namespace" : "sandbox.items",
                "indexFilterSet" : false,
                "parsedQuery" : {

                },
                "queryHash" : "8B3D4AB8",
                "planCacheKey" : "8B3D4AB8",
                "winningPlan" : {
                        "stage" : "FETCH",
                        "inputStage" : {
                                "stage" : "IXSCAN",
                                "keyPattern" : {
                                        "memo" : 1
                                },
                                "indexName" : "memo_1",
                                "isMultiKey" : false,
                                "multiKeyPaths" : {
                                        "memo" : [ ]
                                },
                                "isUnique" : false,
                                "isSparse" : false,
                                "isPartial" : false,
                                "indexVersion" : 2,
                                "direction" : "forward",
                                "indexBounds" : {
                                        "memo" : [
                                                "[MinKey, MaxKey]"
                                        ]
                                }
                        }
                },
                "rejectedPlans" : [ ]
        },
        "serverInfo" : {
                "host" : "aa8a63a66c06",
                "port" : 27017,
                "version" : "4.2.1",
                "gitVersion" : "edf6d45851c0b9ee15548f0f847df141764a317e"
        },
        "ok" : 1
}

queryPlanner.winningPlan を見れば stage あるいは inputStage.stage にてインデックスの利用有無が確認できる。

ハッシュを用いて {memo: 1} という形で指定したが name でも指定できる。

> db.items.find().hint('memo_1')
{ "_id" : ObjectId("5dea0c12645c1b6ec8cc356f"), "name" : "item3", "memo" : null }
{ "_id" : ObjectId("5dea0c19645c1b6ec8cc3570"), "name" : "item4" }
{ "_id" : ObjectId("5dea0c0d645c1b6ec8cc356e"), "name" : "item2", "memo" : "" }
{ "_id" : ObjectId("5dea0c07645c1b6ec8cc356d"), "name" : "item1", "memo" : "hoge" }

存在しないインデックスを指定する

インデックスを貼っていないフィールド name のインデックス(をさもあるかのように)指定する。

> db.items.find().hint('name_1')
Error: error: {
        "ok" : 0,
        "errmsg" : "error processing query: ns=sandbox.itemsTree: $and\nSort: {}\nProj: {}\n planner returned error :: caused by :: hint provided does not correspond to an existing index",
        "code" : 2,
        "codeName" : "BadValue"
}

name でなくハッシュで {name: 1} としても同様である。

hint() という名前ではあるが、 意味的にはヒントというよりも強制している といえる。

貼ってあるインデックスと逆順を指定する

{memo: 1} の値の部分をマイナスに変えると降順になる。

> db.items.find().hint({memo: -1})
Error: error: {
        "ok" : 0,
        "errmsg" : "error processing query: ns=sandbox.itemsTree: $and\nSort: {}\nProj: {}\n planner returned error :: caused by :: hint provided does not correspond to an existing index",
        "code" : 2,
        "codeName" : "BadValue"
}

これも残念ながらエラーとなる。
なぜなら、フィールド memo に対して昇順でしかインデックスを貼っていないためだ。

以下のように降順のインデックスを追加すれば良い。

> db.items.createIndex({memo: -1})
{
        "createdCollectionAutomatically" : false,
        "numIndexesBefore" : 2,
        "numIndexesAfter" : 3,
        "ok" : 1
}
> db.items.find().hint({memo: -1})
{ "_id" : ObjectId("5dea0c07645c1b6ec8cc356d"), "name" : "item1", "memo" : "hoge" }
{ "_id" : ObjectId("5dea0c0d645c1b6ec8cc356e"), "name" : "item2", "memo" : "" }
{ "_id" : ObjectId("5dea0c12645c1b6ec8cc356f"), "name" : "item3", "memo" : null }
{ "_id" : ObjectId("5dea0c19645c1b6ec8cc3570"), "name" : "item4" }

hint() を使うときの注意

大抵の場合、アプリケーションから MongoDB にアクセスするなら ODM やそれに類するものを使うと思う。
たとえば Mongoid にも hint() メソッドが用意されている。
Mongoid の hint() で存在しないインデックスを指定すると bad hint というエラーを吐く。

アプリケーション側でインデックスが存在する前提で hint() を使うと、本番 DB でインデックスを貼り忘れたり、インデックスが削除されたりしたタイミングでエラーが大量にでるようになる危険がある。

hint() を使わなくても意図通りのインデックスが使用されるのであればわざわざ使わない方が良い。
使う必要があるときは、そのインデックスの重要性を関係者に周知しておくことが望ましい。
特に MongoDB のバージョンを上げたりするとインデックス選択のロジックが変わったりして意図と異なるインデックスが採用されるようになったりするので、そういう場合に hint() を使いたくなるはずなのでちゃんと特性を理解して使いこなせるようにしたい。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした