Show
Consider the following collection and associated records, via mongosh:
use mongo-8-hint-scan
db.items.createIndex({item_slug: 1, owner_id: 1, item_id: 1}, {name: 'hint-test' })
db.items.insert({item_slug: 'hat' , owner_id: 'alice' , item_id: 'cap' })db.items.insert({item_slug: 'hat' , owner_id: 'alice' , item_id: 'fedora' })db.items.insert({item_slug: 'hat' , owner_id: 'alice' , item_id: 'helmet' })db.items.insert({item_slug: 'boots' , owner_id: 'alice' , item_id: 'moon' })db.items.insert({item_slug: 'hat' , owner_id: 'bob' , item_id: 'cap' })db.items.insert({item_slug: 'hat' , owner_id: 'bob' , item_id: 'tophat' })db.items.insert({item_slug: 'boots' , owner_id: 'bob' , item_id: 'hiking' })
These queries all produce the expected plan, regardless of whether the hint is supplied. It is an IXSCAN with item_slug, owner_id, item_id bounds consistent with previous mongo versions.
db.items.explain().find({item_slug: 'hat' , owner_id: 'alice' })
db.items.explain().find({item_slug: 'hat' , owner_id: 'alice' , item_id: {$in: [ 'cap' ]}})
db.items.explain().find({item_slug: 'hat' , owner_id: 'alice' }).hint( 'hint-test' )
db.items.explain().find({item_slug: 'hat' , owner_id: 'alice' , item_id: {$in: [ 'cap' ]}}).hint( 'hint-test' )
In mongo 8, the following query produces a plan with the single stage, `EOF` (in this example, a single shard)
db.items.explain().find({item_slug: 'hat' , owner_id: 'alice' , item_id: {$in: []}})
...
queryPlanner: {
winningPlan: {
stage: 'SINGLE_SHARD' ,
shards: [
{
explainVersion: '1' ,
shardName: ...,
connectionString: ...,
serverInfo: {
host: ...,
port: ...,
version: '8.0.4' ,
gitVersion: 'bc35ab4305d9920d9d0491c1c9ef9b72383d31f9'
},
namespace: 'mongo-8-hint-scan.items' ,
parsedQuery: { '$alwaysFalse' : 1 },
indexFilterSet: false ,
planCacheShapeHash: ...,
planCacheKey: ...,
optimizationTimeMillis: 0,
maxIndexedOrSolutionsReached: false ,
maxIndexedAndSolutionsReached: false ,
maxScansToExplodeReached: false ,
prunedSimilarIndexes: false ,
winningPlan: { isCached: false , stage: 'EOF' },
rejectedPlans: []
}
]
}
},
...
However, if an index hint is supplied, the plan produces a full scan of the index and collection, though the FETCH stage does include {{{}
{ '$alwaysFalse': 1 }
{}}}.
db.items.explain().find({item_slug: 'hat' , owner_id: 'alice' , item_id: {$in: []}}).hint( 'hint-test' )
...
queryPlanner: {
winningPlan: {
stage: 'SINGLE_SHARD' ,
shards: [
{
explainVersion: '1' ,
shardName: ...,
connectionString: ...,
serverInfo: {
host: ...,
port: ...,
version: '8.0.4' ,
gitVersion: 'bc35ab4305d9920d9d0491c1c9ef9b72383d31f9'
},
namespace: 'mongo-8-hint-scan.items' ,
parsedQuery: { '$alwaysFalse' : 1 },
indexFilterSet: false ,
planCacheShapeHash: ...,
planCacheKey: ...,
optimizationTimeMillis: 0,
maxIndexedOrSolutionsReached: false ,
maxIndexedAndSolutionsReached: false ,
maxScansToExplodeReached: false ,
prunedSimilarIndexes: false ,
winningPlan: {
isCached: false ,
stage: 'FETCH' ,
filter: { '$alwaysFalse' : 1 },
inputStage: {
stage: 'IXSCAN' ,
keyPattern: { item_slug: 1, owner_id: 1, item_id: 1 },
indexName: 'hint-test' ,
isMultiKey: false ,
multiKeyPaths: { item_slug: [], owner_id: [], item_id: [] },
isUnique: false ,
isSparse: false ,
isPartial: false ,
indexVersion: 2,
direction: 'forward' ,
indexBounds: {
item_slug: [ '[MinKey, MaxKey]' ],
owner_id: [ '[MinKey, MaxKey]' ],
item_id: [ '[MinKey, MaxKey]' ]
}
}
},
rejectedPlans: []
}
]
}
},
...
In mongo 7, the query planner produces a query that will require work to be performed but it has an efficient IXSCAN of the index.
db.items.explain().find({item_slug: 'hat' , owner_id: 'alice' , item_id: {$in: []}}).hint( 'hint-test' )
...
queryPlanner: {
mongosPlannerVersion: 1,
winningPlan: {
stage: 'SINGLE_SHARD' ,
shards: [
{
shardName: ...,
connectionString: ...,
serverInfo: {
host: ...,
port: 27018,
version: '7.0.11' ,
gitVersion: 'f451220f0df2b9dfe073f1521837f8ec5c208a8c'
},
namespace: 'mongo-8-hint-scan.items' ,
indexFilterSet: false ,
parsedQuery: {
'$and' : [
{ item_slug: { '$eq' : 'hat' } },
{ owner_id: { '$eq' : 'alice' } },
{ item_id: { '$in' : [] } }
]
},
queryHash: ...,
planCacheKey: ...,
maxIndexedOrSolutionsReached: false ,
maxIndexedAndSolutionsReached: false ,
maxScansToExplodeReached: false ,
winningPlan: {
stage: 'FETCH' ,
inputStage: {
stage: 'IXSCAN' ,
keyPattern: { item_slug: 1, owner_id: 1, item_id: 1 },
indexName: 'hint-test' ,
isMultiKey: false ,
multiKeyPaths: { item_slug: [], owner_id: [], item_id: [] },
isUnique: false ,
isSparse: false ,
isPartial: false ,
indexVersion: 2,
direction: 'forward' ,
indexBounds: {
item_slug: [ '[ "hat" , "hat" ]' ],
owner_id: [ '[ "alice" , "alice" ]' ],
item_id: []
}
}
},
rejectedPlans: []
}
]
}
},
The results are similar when combined with $or . In the case where a query with an empty $in clause is the only predicate in the list, the planner evaluates that as a single query, and exhibits the above documented behavior.
db.items.explain().find({$or:[{item_slug: 'hat' , owner_id: 'alice' , item_id: {$in: []}}]}).hint( 'hint-test' )
{
queryPlanner: {
winningPlan: {
stage: 'SINGLE_SHARD' ,
shards: [
{
explainVersion: '1' ,
shardName: ...,
connectionString: ...,
serverInfo: {
host: ...,
port: 27018,
version: '8.0.4' ,
gitVersion: 'bc35ab4305d9920d9d0491c1c9ef9b72383d31f9'
},
namespace: 'mongo-8-hint-scan.items' ,
parsedQuery: { '$alwaysFalse' : 1 },
indexFilterSet: false ,
planCacheShapeHash: ...,
planCacheKey: ...,
optimizationTimeMillis: 0,
maxIndexedOrSolutionsReached: false ,
maxIndexedAndSolutionsReached: false ,
maxScansToExplodeReached: false ,
prunedSimilarIndexes: false ,
winningPlan: {
isCached: false ,
stage: 'FETCH' ,
filter: { '$alwaysFalse' : 1 },
inputStage: {
stage: 'IXSCAN' ,
keyPattern: { item_slug: 1, owner_id: 1, item_id: 1 },
indexName: 'hint-test' ,
isMultiKey: false ,
multiKeyPaths: { item_slug: [], owner_id: [], item_id: [] },
isUnique: false ,
isSparse: false ,
isPartial: false ,
indexVersion: 2,
direction: 'forward' ,
indexBounds: {
item_slug: [ '[MinKey, MaxKey]' ],
owner_id: [ '[MinKey, MaxKey]' ],
item_id: [ '[MinKey, MaxKey]' ]
}
}
},
rejectedPlans: []
}
]
}
},
If one or more elements of the $or have a proper $in clause, the query plan will skip the EOF plan and properly handle the remaining elements of the $or . This works with and without an index hint.
db.items.explain().find({$or:[{item_slug: 'hat' , owner_id: 'alice' , item_id: {$in: []}}, {item_slug: 'boots' , owner_id: 'alice' , item_id: {$in: [ 'dlc_1' ]}}]}).hint( 'hint-test' )
{
queryPlanner: {
winningPlan: {
stage: 'SINGLE_SHARD' ,
shards: [
{
explainVersion: '1' ,
shardName: ...,
connectionString: ...,
serverInfo: {
host: ...,
port: 27018,
version: '8.0.4' ,
gitVersion: 'bc35ab4305d9920d9d0491c1c9ef9b72383d31f9'
},
namespace: 'mongo-8-hint-scan.items' ,
parsedQuery: {
'$and' : [
{ item_id: { '$eq' : 'dlc_1' } },
{ item_slug: { '$eq' : 'boots' } },
{ owner_id: { '$eq' : 'alice' } }
]
},
indexFilterSet: false ,
planCacheShapeHash: ...,
planCacheKey: ...,
optimizationTimeMillis: 0,
maxIndexedOrSolutionsReached: false ,
maxIndexedAndSolutionsReached: false ,
maxScansToExplodeReached: false ,
prunedSimilarIndexes: false ,
winningPlan: {
isCached: false ,
stage: 'FETCH' ,
inputStage: {
stage: 'IXSCAN' ,
keyPattern: { item_slug: 1, owner_id: 1, item_id: 1 },
indexName: 'hint-test' ,
isMultiKey: false ,
multiKeyPaths: { item_slug: [], owner_id: [], item_id: [] },
isUnique: false ,
isSparse: false ,
isPartial: false ,
indexVersion: 2,
direction: 'forward' ,
indexBounds: {
item_slug: [ '[ "boots" , "boots" ]' ],
owner_id: [ '[ "alice" , "alice" ]' ],
item_id: [ '[ "dlc_1" , "dlc_1" ]' ]
}
}
},
rejectedPlans: []
}
]
}
},
Similarly, an $and clause with one or more $in: [] in its queries, will cause a full collection scan when a hint is supplied.
db.items.explain().find({$and:[{item_slug: 'hat' , owner_id: 'alice' , item_id: {$in: []}}, {item_slug: 'boots' , owner_id: 'alice' , item_id: {$in: [ 'dlc_1' ]}}]}).hint( 'hint-test' )
{
queryPlanner: {
winningPlan: {
stage: 'SINGLE_SHARD' ,
shards: [
{
explainVersion: '1' ,
shardName: ...,
connectionString: ...,
serverInfo: {
host: ...,
port: 27018,
version: '8.0.4' ,
gitVersion: 'bc35ab4305d9920d9d0491c1c9ef9b72383d31f9'
},
namespace: 'mongo-8-hint-scan.items' ,
parsedQuery: { '$alwaysFalse' : 1 },
indexFilterSet: false ,
planCacheShapeHash: ...,
planCacheKey: ...,
optimizationTimeMillis: 0,
maxIndexedOrSolutionsReached: false ,
maxIndexedAndSolutionsReached: false ,
maxScansToExplodeReached: false ,
prunedSimilarIndexes: false ,
winningPlan: {
isCached: false ,
stage: 'FETCH' ,
filter: { '$alwaysFalse' : 1 },
inputStage: {
stage: 'IXSCAN' ,
keyPattern: { item_slug: 1, owner_id: 1, item_id: 1 },
indexName: 'hint-test' ,
isMultiKey: false ,
multiKeyPaths: { item_slug: [], owner_id: [], item_id: [] },
isUnique: false ,
isSparse: false ,
isPartial: false ,
indexVersion: 2,
direction: 'forward' ,
indexBounds: {
item_slug: [ '[MinKey, MaxKey]' ],
owner_id: [ '[MinKey, MaxKey]' ],
item_id: [ '[MinKey, MaxKey]' ]
}
}
},
rejectedPlans: []
}
]
}
},