When indexing array fields, most indexes contain one key per element of the array, hence the terminology "multikey indexes" (documented here). However, compound "2d" indexes use a different format for the non-2d fields. Namely, all of the array elements are stored in a single key whose value is itself an array. Consider the following example:
> db.c.drop() false > db.c.createIndex({a: "2d", "b.c": 1}) { "createdCollectionAutomatically" : true, "numIndexesBefore" : 1, "numIndexesAfter" : 2, "ok" : 1 } > db.c.insert({a: [0, 0], b: [{c: 1}, {c: 2}]}) WriteResult({ "nInserted" : 1 }) > db.c.find().hint({a: "2d", "b.c": 1}).returnKey() { "a" : BinData(128,"wAAAAAAAAAA="), "b.c" : [ 1, 2 ] }
As you can see, the inserted document leads to just a single index key. The value of this index key for the "b.c" field is the array [1, 2].
Because of this index format, predicates against the trailing fields of an index typically are not used to generate bounds against the index. A predicate like {"b.c": {$eq: 2}} would normally result in point lookup in the index for the value 2. However, this would incorrectly miss the above document, because the index key value is the entire array [1, 2]. Instead, the predicate is attached as a filter to the IXSCAN stage, as you can see from the explain of the query below:
> db.c.find({a: {$geoWithin: {$center: [[0, 0], 1]}}, "b.c": {$eq: 2}}).explain() { "queryPlanner" : { ... "inputStage" : { "stage" : "IXSCAN", "filter" : { "b.c" : { "$eq" : 2 } }, "keyPattern" : { "a" : "2d", "b.c" : 1 }, "indexName" : "a_2d_b.c_1", "isMultiKey" : false, "isUnique" : false, "isSparse" : false, "isPartial" : false, "indexVersion" : 2, "direction" : "forward", "indexBounds" : { "a" : [ "[BinData(128, 3FFF300000000000), BinData(128, 3FFF3FFFFFFFFFFF)]", "[BinData(128, 3FFF400000000000), BinData(128, 3FFF7FFFFFFFFFFF)]", "[BinData(128, 3FFF800000000000), BinData(128, 3FFFBFFFFFFFFFFF)]", "[BinData(128, 3FFFC00000000000), BinData(128, 3FFFFFFFFFFFFFFF)]", "[BinData(128, 6AAA000000000000), BinData(128, 6AAA3FFFFFFFFFFF)]", "[BinData(128, 6AAA600000000000), BinData(128, 6AAA6FFFFFFFFFFF)]", "[BinData(128, 6AAA800000000000), BinData(128, 6AAABFFFFFFFFFFF)]", "[BinData(128, 6AAAC00000000000), BinData(128, 6AAAFFFFFFFFFFFF)]", "[BinData(128, 9555000000000000), BinData(128, 95553FFFFFFFFFFF)]", "[BinData(128, 9555400000000000), BinData(128, 95557FFFFFFFFFFF)]", "[BinData(128, 9555900000000000), BinData(128, 95559FFFFFFFFFFF)]", "[BinData(128, 9555C00000000000), BinData(128, 9555FFFFFFFFFFFF)]", "[BinData(128, C000000000000000), BinData(128, C0003FFFFFFFFFFF)]", "[BinData(128, C000400000000000), BinData(128, C0007FFFFFFFFFFF)]", "[BinData(128, C000800000000000), BinData(128, C000BFFFFFFFFFFF)]", "[BinData(128, C000C00000000000), BinData(128, C000CFFFFFFFFFFF)]" ], "b.c" : [ "[MinKey, MaxKey]" ] } } }, "rejectedPlans" : [ ] }, ... }
When the predicate over the trailing field is an $elemMatch, however, the planner incorrectly generates bounds:
> db.c.find({a: {$geoWithin: {$center: [[0, 0], 1]}}, b: {$elemMatch: {c: 1}}}).explain() { "queryPlanner" : { ... "inputStage" : { "stage" : "IXSCAN", "keyPattern" : { "a" : "2d", "b.c" : 1 }, "indexName" : "a_2d_b.c_1", "isMultiKey" : false, "isUnique" : false, "isSparse" : false, "isPartial" : false, "indexVersion" : 2, "direction" : "forward", "indexBounds" : { "a" : [ "[BinData(128, 3FFF300000000000), BinData(128, 3FFF3FFFFFFFFFFF)]", "[BinData(128, 3FFF400000000000), BinData(128, 3FFF7FFFFFFFFFFF)]", "[BinData(128, 3FFF800000000000), BinData(128, 3FFFBFFFFFFFFFFF)]", "[BinData(128, 3FFFC00000000000), BinData(128, 3FFFFFFFFFFFFFFF)]", "[BinData(128, 6AAA000000000000), BinData(128, 6AAA3FFFFFFFFFFF)]", "[BinData(128, 6AAA600000000000), BinData(128, 6AAA6FFFFFFFFFFF)]", "[BinData(128, 6AAA800000000000), BinData(128, 6AAABFFFFFFFFFFF)]", "[BinData(128, 6AAAC00000000000), BinData(128, 6AAAFFFFFFFFFFFF)]", "[BinData(128, 9555000000000000), BinData(128, 95553FFFFFFFFFFF)]", "[BinData(128, 9555400000000000), BinData(128, 95557FFFFFFFFFFF)]", "[BinData(128, 9555900000000000), BinData(128, 95559FFFFFFFFFFF)]", "[BinData(128, 9555C00000000000), BinData(128, 9555FFFFFFFFFFFF)]", "[BinData(128, C000000000000000), BinData(128, C0003FFFFFFFFFFF)]", "[BinData(128, C000400000000000), BinData(128, C0007FFFFFFFFFFF)]", "[BinData(128, C000800000000000), BinData(128, C000BFFFFFFFFFFF)]", "[BinData(128, C000C00000000000), BinData(128, C000CFFFFFFFFFFF)]" ], "b.c" : [ "[1.0, 1.0]" ] } } }, "rejectedPlans" : [ ] }, ... }
As a result, this query misses the matching document:
> db.c.find({a: {$geoWithin: {$center: [[0, 0], 1]}}, b: {$elemMatch: {c: 1}}});
// No results.
- is related to
-
SERVER-31444 Queries against multikey trailing fields of a compound 2d index are incorrectly covered, leading to incorrect results
- Closed
-
SERVER-21011 Certain queries against compound 2d/text indexes are incorrectly covered, return incorrect results
- Closed