ワイルドカードインデックスと通常のインデックスはどちらが優先されるか

MongoDB 4.2 でワイルドカードインデックスという機能が追加された。

# ワイルドカードインデックス

`$**` というキーワードを使うことで、複数のフィールド（あるいはサブフィールド）に対してまとめてインデックスを貼ることができる。

```js
> db.users.createIndex({"addr.$**": 1})
{
        "createdCollectionAutomatically" : false,
        "numIndexesBefore" : 2,
        "numIndexesAfter" : 3,
        "ok" : 1
}
```

というインデックスを作れば `addr.zipcode` や `addr.tel` 、 `addr.prefecture` といった各サブフィールドに対してのインデックスが有効になる。

```js
> db.users.createIndex({"$**": 1})
{
        "createdCollectionAutomatically" : false,
        "numIndexesBefore" : 3,
        "numIndexesAfter" : 4,
        "ok" : 1
}
```

というインデックスを作れば `name` や `age` といったフィールドに対してのインデックスが有効になる。

## ワイルドカードインデックスのメリット

ワイルドカードインデックスには、複数のインデックス指定をまとめることができる他に1つ明確なメリットがある。
それは、先に設定してあれば、その後に追加されたフィールドに対しても対象であれば indexing が行われることだ。

たとえば以下のように `name` と `memo` のみをフィールドに持つ `users` コレクションを用意して、ワイルドカードインデックスを設定する。

```js
> db.users.find()
{ "_id" : ObjectId("5deb5872db924e63ac8f4f11"), "name" : "John", "memo" : "first" }
> db.users.getIndexes()
[
        {
                "v" : 2,
                "key" : {
                        "_id" : 1
                },
                "name" : "_id_",
                "ns" : "sandbox.users"
        },
        {
                "v" : 2,
                "key" : {
                        "$**" : 1
                },
                "name" : "$**_1",
                "ns" : "sandbox.users"
        }
]
```

この状況で新たな新たなフィールド `age` を含むドキュメントを追加して `age` で絞り込み検索すると以下のように、インデックススキャンされる。

```js
> db.users.insert({name: "Tom", memo: "second", age: 18})
WriteResult({ "nInserted" : 1 })
> db.users.find({age: {$lt: 20}}).explain()
{
        "queryPlanner" : {
                "plannerVersion" : 1,
                "namespace" : "sandbox.users",
                "indexFilterSet" : false,
                "parsedQuery" : {
                        "age" : {
                                "$lt" : 20
                        }
                },
                "queryHash" : "C3B4D0B9",
                "planCacheKey" : "EB168F30",
                "winningPlan" : {
                        "stage" : "FETCH",
                        "inputStage" : {
                                "stage" : "IXSCAN",
                                "keyPattern" : {
                                        "$_path" : 1,
                                        "age" : 1
                                },
                                "indexName" : "$**_1",
                                "isMultiKey" : false,
                                "multiKeyPaths" : {
                                        "$_path" : [ ],
                                        "age" : [ ]
                                },
                                "isUnique" : false,
                                "isSparse" : false,
                                "isPartial" : false,
                                "indexVersion" : 2,
                                "direction" : "forward",
                                "indexBounds" : {
                                        "$_path" : [
                                                "[\"age\", \"age\"]"
                                        ],
                                        "age" : [
                                                "[-inf.0, 20.0)"
                                        ]
                                }
                        }
                },
                "rejectedPlans" : [ ]
        },
        "serverInfo" : {
                "host" : "aa8a63a66c06",
                "port" : 27017,
                "version" : "4.2.1",
                "gitVersion" : "edf6d45851c0b9ee15548f0f847df141764a317e"
        },
        "ok" : 1
}
```

## ワイルドカードインデックスの制約

ワイルドカードインデックスにはいくつかの制約がある。

* 複合インデックスに対応していない（上の例を見れば容易に想像できるかと思いますが）
* unique 制約をつけられない
* テキストインデックスや TTL インデックス等に対応していない

# 通常のインデックス指定との併用

ワイルドカードインデックスが設定されている状況で、対象のフィールドに同じインデックスを個別に貼ることができる。
あるいは逆に既にインデックスが貼られているフィールドを含む形でワイルドカードインデックスを設定することもできる。

`memo` に対して個別にインデックスを貼って、同時にワイルドカードインデックスも設定済みとする。

```js
> db.users.getIndexes()
[
        {
                "v" : 2,
                "key" : {
                        "_id" : 1
                },
                "name" : "_id_",
                "ns" : "sandbox.users"
        },
        {
                "v" : 2,
                "key" : {
                        "memo" : 1
                },
                "name" : "memo_1",
                "ns" : "sandbox.users"
        },
        {
                "v" : 2,
                "key" : {
                        "$**" : 1
                },
                "name" : "$**_1",
                "ns" : "sandbox.users"
        }
]
```

この状況で `memo` で絞り込み検索をするとどちらのインデックスが使われるか。

```js
> db.users.find({memo: 'hoge'}).explain()
{
        "queryPlanner" : {
                "plannerVersion" : 1,
                "namespace" : "sandbox.users",
                "indexFilterSet" : false,
                "parsedQuery" : {
                        "memo" : {
                                "$eq" : "hoge"
                        }
                },
                "queryHash" : "4131DEB4",
                "planCacheKey" : "98385470",
                "winningPlan" : {
                        "stage" : "FETCH",
                        "inputStage" : {
                                "stage" : "IXSCAN",
                                "keyPattern" : {
                                        "$_path" : 1,
                                        "memo" : 1
                                },
                                "indexName" : "$**_1",
                                "isMultiKey" : false,
                                "multiKeyPaths" : {
                                        "$_path" : [ ],
                                        "memo" : [ ]
                                },
                                "isUnique" : false,
                                "isSparse" : false,
                                "isPartial" : false,
                                "indexVersion" : 2,
                                "direction" : "forward",
                                "indexBounds" : {
                                        "$_path" : [
                                                "[\"memo\", \"memo\"]"
                                        ],
                                        "memo" : [
                                                "[\"hoge\", \"hoge\"]"
                                        ]
                                }
                        }
                },
                "rejectedPlans" : [
                        {
                                "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" : [
                                                        "[\"hoge\", \"hoge\"]"
                                                ]
                                        }
                                }
                        }
                ]
        },
        "serverInfo" : {
                "host" : "aa8a63a66c06",
                "port" : 27017,
                "version" : "4.2.1",
                "gitVersion" : "edf6d45851c0b9ee15548f0f847df141764a317e"
        },
        "ok" : 1
}
```

ワイルドカードインデックスが採用される。