MongoDB 4.2 でワイルドカードインデックスという機能が追加された。
ワイルドカードインデックス
$**
というキーワードを使うことで、複数のフィールド(あるいはサブフィールド)に対してまとめてインデックスを貼ることができる。
> db.users.createIndex({"addr.$**": 1})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 2,
"numIndexesAfter" : 3,
"ok" : 1
}
というインデックスを作れば addr.zipcode
や addr.tel
、 addr.prefecture
といった各サブフィールドに対してのインデックスが有効になる。
> db.users.createIndex({"$**": 1})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 3,
"numIndexesAfter" : 4,
"ok" : 1
}
というインデックスを作れば name
や age
といったフィールドに対してのインデックスが有効になる。
ワイルドカードインデックスのメリット
ワイルドカードインデックスには、複数のインデックス指定をまとめることができる他に1つ明確なメリットがある。
それは、先に設定してあれば、その後に追加されたフィールドに対しても対象であれば indexing が行われることだ。
たとえば以下のように name
と memo
のみをフィールドに持つ users
コレクションを用意して、ワイルドカードインデックスを設定する。
> 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
で絞り込み検索すると以下のように、インデックススキャンされる。
> 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
に対して個別にインデックスを貼って、同時にワイルドカードインデックスも設定済みとする。
> 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
で絞り込み検索をするとどちらのインデックスが使われるか。
> 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
}
ワイルドカードインデックスが採用される。