一、MongoDB如何选择索引

如果我们在Collection建了5个index,那么当我们查询的时候,MongoDB会根据查询语句的筛选条件、sort排序等来定位可以使用的index作为候选索引;然后MongoDB会创建对应数量的查询计划,并分别使用不同线程执行查询计划,最终会选择一个执行最快的index;但是这个选择也不是一成不变的,后续还会有一段时间根据实际执行情况动态调整;

二、数据准备

for(let i = 0;i<1000000;i++){
db.users.insertOne({
"id":i,
"name":'user'+i,
"age":Math.floor(Math.random()*120),
"created":new Date(ISODate().getTime() - 1000 * 60*i)
});
}

三、正则对index的使用

MongoDB支持正则查询,在特定的情况其也是可以利用index获得查询性能的提升;

虽然MongDB执行正则会最大限度的使用index,但是不同的用法还是会影响对index的利用程度的;

执行以下普通正则表达式

从queryPlanner.winningPlan部分的COLLSCAN,可以看到正则表达式默认会进行全表的扫描;

从executionStats.executionStages部分可以看到COLLSCAN共扫描了1000000个文档,并返回1111个文档,总耗时794ms;

db.users.find({
name:/user999/
}).explain('executionStats') {
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.users",
"indexFilterSet" : false,
"winningPlan" : {
"stage" : "COLLSCAN",
"filter" : {
"name" : {
"$regex" : "user999"
}
},
"direction" : "forward"
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 1111,
"executionTimeMillis" : 909,
"totalKeysExamined" : 0,
"totalDocsExamined" : 1000000,
"executionStages" : {
"stage" : "COLLSCAN",
"filter" : {
"name" : {
"$regex" : "user999"
}
},
"nReturned" : 1111,
"executionTimeMillisEstimate" : 794,
"works" : 1000002,
"advanced" : 1111,
"needTime" : 998890,
"needYield" : 0,
"saveState" : 7830,
"restoreState" : 7830,
"isEOF" : 1,
"invalidates" : 0,
"direction" : "forward",
"docsExamined" : 1000000
}
}
}

创建一个包含name的index;

db.users.createIndex({name:1})

再次执行上边的查询,可以看到使用了我们新建的name_1索引;但是从执行状态来看,还是扫描了全体的索引的key,并不能很好的利用index;

{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.users",
"indexFilterSet" : false,
"parsedQuery" : {
"name" : {
"$regex" : "user999"
}
},
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"filter" : {
"name" : {
"$regex" : "user999"
}
},
"keyPattern" : {
"name" : 1
},
"indexName" : "name_1"
}
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 1111,
"executionTimeMillis" : 971,
"totalKeysExamined" : 1000000,
"totalDocsExamined" : 1111,
"executionStages" : {
"stage" : "FETCH",
"nReturned" : 1111,
"executionTimeMillisEstimate" : 887,
"docsExamined" : 1111,
"alreadyHasObj" : 0,
"inputStage" : {
"stage" : "IXSCAN",
"filter" : {
"name" : {
"$regex" : "user999"
}
},
"nReturned" : 1111,
"executionTimeMillisEstimate" : 876,
"keyPattern" : {
"name" : 1
},
"indexName" : "name_1",
"keysExamined" : 1000000
}
}
}
}

使用前缀匹配的话可以最大限度的利用index,从执行状态可以看到只检测了1111个index key;

db.users.find({
name:/^user999/
}).explain('executionStats') {
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.users",
"indexFilterSet" : false,
"parsedQuery" : {
"name" : {
"$regex" : "^user999"
}
},
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"name" : 1
},
"indexName" : "name_1"
}
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 1111,
"executionTimeMillis" : 2,
"totalKeysExamined" : 1111,
"totalDocsExamined" : 1111,
"executionStages" : {
"stage" : "FETCH",
"nReturned" : 1111,
"executionTimeMillisEstimate" : 0
"docsExamined" : 1111
"inputStage" : {
"stage" : "IXSCAN",
"nReturned" : 1111,
"executionTimeMillisEstimate" : 0,
"indexName" : "name_1",
"keysExamined" : 1111
}
}
}
}

即使是前缀匹配,如果忽略大小写的话也无法充分利用index了;

db.users.find({
name:/^user999/i
}).explain('executionStats') {
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.users",
"indexFilterSet" : false,
"parsedQuery" : {
"name" : {
"$regex" : "user999",
"$options" : "i"
}
},
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"filter" : {
"name" : {
"$regex" : "user999",
"$options" : "i"
}
},
"keyPattern" : {
"name" : 1
},
"indexName" : "name_1"
}
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 1111,
"executionTimeMillis" : 943,
"totalKeysExamined" : 1000000,
"totalDocsExamined" : 1111,
"executionStages" : {
"stage" : "FETCH",
"nReturned" : 1111,
"executionTimeMillisEstimate" : 833,
"works" : 1000001,
"inputStage" : {
"stage" : "IXSCAN",
"filter" : {
"name" : {
"$regex" : "user999",
"$options" : "i"
}
},
"nReturned" : 1111,
"executionTimeMillisEstimate" : 833,
"keyPattern" : {
"name" : 1
},
"indexName" : "name_1"
"keysExamined" : 1000000
}
}
}
}

四、$or从句对索引的利用

MongoDB执行$or从句的时候,会将所有的从句作为逻辑的整体,要不就都使用index,要不就都进行全表扫描;

执行以下的查询语句;

db.users.find({
$or:[
{name:/^user666/},
{age:{$gte:80}}
]
}).explain('executionStats')

在只有name_1这个index的时候,我们可以看到MongoDB进行了全表扫描,全表扫描的时候进行$or从句的过滤;

{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.users",
"indexFilterSet" : false,
"parsedQuery" : {
"$or" : [
{
"age" : {
"$gte" : 20
}
},
{
"name" : {
"$regex" : "^user666"
}
}
]
},
"winningPlan" : {
"stage" : "SUBPLAN",
"inputStage" : {
"stage" : "COLLSCAN",
"filter" : {
"$or" : [
{
"age" : {
"$gte" : 20
}
},
{
"name" : {
"$regex" : "^user666"
}
}
]
},
"direction" : "forward"
}
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 833995,
"executionTimeMillis" : 576,
"totalKeysExamined" : 0,
"totalDocsExamined" : 1000000,
"executionStages" : {
"stage" : "SUBPLAN",
"nReturned" : 833995,
"executionTimeMillisEstimate" : 447,
"inputStage" : {
"stage" : "COLLSCAN",
"filter" : {
"$or" : [
{
"age" : {
"$gte" : 20
}
},
{
"name" : {
"$regex" : "^user666"
}
}
]
},
"nReturned" : 833995,
"executionTimeMillisEstimate" : 447,
"docsExamined" : 1000000
}
}
}
}

我们对name字段新建一个index;

db.users.createIndex({age:1})

再次执行以上的查询语句,这次可以看到每个从句都利用了index,并且每个从句会单独执行并最终进行or操作;

{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.users",
"indexFilterSet" : false,
"parsedQuery" : {
"$or" : [
{
"age" : {
"$gte" : 80
}
},
{
"name" : {
"$regex" : "^user666"
}
}
]
},
"winningPlan" : {
"stage" : "SUBPLAN",
"inputStage" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "OR",
"inputStages" : [
{
"stage" : "IXSCAN",
"keyPattern" : {
"name" : 1
},
"indexName" : "name_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"name" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"name" : [
"[\"user666\", \"user667\")",
"[/^user666/, /^user666/]"
]
}
},
{
"stage" : "IXSCAN",
"keyPattern" : {
"age" : 1
},
"indexName" : "age_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"age" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"age" : [
"[80.0, inf.0]"
]
}
}
]
}
}
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 333736,
"executionTimeMillis" : 741,
"totalKeysExamined" : 334102,
"totalDocsExamined" : 333736,
"executionStages" : {
"stage" : "SUBPLAN",
"nReturned" : 333736,
"executionTimeMillisEstimate" : 703,
"inputStage" : {
"stage" : "FETCH",
"nReturned" : 333736,
"executionTimeMillisEstimate" : 682
"docsExamined" : 333736,
"inputStage" : {
"stage" : "OR",
"nReturned" : 333736,
"executionTimeMillisEstimate" : 366,
"inputStages" : [
{
"stage" : "IXSCAN",
"nReturned" : 1111,
"executionTimeMillisEstimate" : 0,
"keyPattern" : {
"name" : 1
},
"indexName" : "name_1",
"indexBounds" : {
"name" : [
"[\"user666\", \"user667\")",
"[/^user666/, /^user666/]"
]
},
"keysExamined" : 1112
},
{
"stage" : "IXSCAN",
"nReturned" : 332990,
"executionTimeMillisEstimate" : 212,
"keyPattern" : {
"age" : 1
},
"indexName" : "age_1",
"indexBounds" : {
"age" : [
"[80.0, inf.0]"
]
},
"keysExamined" : 332990
}
]
}
}
}
}
}

五、sort对索引的利用

如果sort操作无法利用index,则MongoDB就会在内存中排序数据,并且数据量一大就会报错;

db.users.find().sort({created: -1}).explain('executionStats')

{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.users",
"indexFilterSet" : false,
"parsedQuery" : { },
"winningPlan" : {
"stage" : "SORT",
"sortPattern" : {
"created" : -1
},
"inputStage" : {
"stage" : "SORT_KEY_GENERATOR",
"inputStage" : {
"stage" : "COLLSCAN",
"direction" : "forward"
}
}
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : false,
"errorMessage" : "Exec error resulting in state FAILURE :: caused by :: Sort operation used more than the maximum 33554432 bytes of RAM. Add an index, or specify a smaller limit.",
"errorCode" : 96,
"nReturned" : 0,
"executionTimeMillis" : 959,
"totalKeysExamined" : 0,
"totalDocsExamined" : 361996,
"executionStages" : {
"stage" : "SORT",
"nReturned" : 0,
"executionTimeMillisEstimate" : 922,
"sortPattern" : {
"created" : -1
},
"memUsage" : 33554518,
"memLimit" : 33554432,
"inputStage" : {
"stage" : "SORT_KEY_GENERATOR",
"nReturned" : 361996,
"executionTimeMillisEstimate" : 590,
"inputStage" : {
"stage" : "COLLSCAN",
"nReturned" : 361996,
"executionTimeMillisEstimate" : 147,
"direction" : "forward",
"docsExamined" : 361996
}
}
}
}
}

如果是单字段index,sort从两个方向都可以充分利用index;可以看到MongoDB直接按照index的顺序返回结果,直接就没有sort阶段了;

db.users.find().sort({name: -1}).explain('executionStats')

{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.users",
"indexFilterSet" : false,
"parsedQuery" : { },
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"name" : 1
},
"indexName" : "name_1",
"direction" : "backward",
"indexBounds" : {
"name" : [
"[MaxKey, MinKey]"
]
}
}
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 1000000,
"executionTimeMillis" : 1317,
"totalKeysExamined" : 1000000,
"totalDocsExamined" : 1000000,
"executionStages" : {
"stage" : "FETCH",
"nReturned" : 1000000,
"executionTimeMillisEstimate" : 1180,
"inputStage" : {
"stage" : "IXSCAN",
"nReturned" : 1000000,
"executionTimeMillisEstimate" : 560,
"keyPattern" : {
"name" : 1
},
"indexName" : "name_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"name" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "backward",
"indexBounds" : {
"name" : [
"[MaxKey, MinKey]"
]
},
"keysExamined" : 1000000,
"seeks" : 1,
"dupsTested" : 0,
"dupsDropped" : 0,
"seenInvalidated" : 0
}
}
}
}

对于复合索引,sort除了可以从整体上从两个方向利用index,也可以利用index的前缀索引和非前缀局部索引;

新建复合索引

db.users.createIndex({created:-1, name:1, age:1})

按照复合索引的反方向进行整体排序;

db.users.find().sort({created:1, name:-1, age:-1}).explain('executionStats')

{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.users",
"indexFilterSet" : false,
"parsedQuery" : { },
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"created" : -1,
"name" : 1,
"age" : 1
},
"indexName" : "created_-1_name_1_age_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"created" : [ ],
"name" : [ ],
"age" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "backward",
"indexBounds" : {
"created" : [
"[MinKey, MaxKey]"
],
"name" : [
"[MaxKey, MinKey]"
],
"age" : [
"[MaxKey, MinKey]"
]
}
}
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 1000000,
"executionTimeMillis" : 1518,
"totalKeysExamined" : 1000000,
"totalDocsExamined" : 1000000,
"executionStages" : {
"stage" : "FETCH",
"nReturned" : 1000000,
"executionTimeMillisEstimate" : 1364,
"docsExamined" : 1000000,
"inputStage" : {
"stage" : "IXSCAN",
"nReturned" : 1000000,
"executionTimeMillisEstimate" : 816,
"keyPattern" : {
"created" : -1,
"name" : 1,
"age" : 1
},
"indexName" : "created_-1_name_1_age_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"created" : [ ],
"name" : [ ],
"age" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "backward",
"indexBounds" : {
"created" : [
"[MinKey, MaxKey]"
],
"name" : [
"[MaxKey, MinKey]"
],
"age" : [
"[MaxKey, MinKey]"
]
},
"keysExamined" : 1000000
}
}
}
}

排序使用索引前缀,也需要保证字段的顺序,但是可以反方向排序;

db.users.find().sort({created:1, name:-1, age:-1}).explain('executionStats')

{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.users",
"indexFilterSet" : false,
"parsedQuery" : { },
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"created" : -1,
"name" : 1,
"age" : 1
},
"indexName" : "created_-1_name_1_age_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"created" : [ ],
"name" : [ ],
"age" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "backward",
"indexBounds" : {
"created" : [
"[MinKey, MaxKey]"
],
"name" : [
"[MaxKey, MinKey]"
],
"age" : [
"[MaxKey, MinKey]"
]
}
}
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 1000000,
"executionTimeMillis" : 1487,
"totalKeysExamined" : 1000000,
"totalDocsExamined" : 1000000,
"executionStages" : {
"stage" : "FETCH",
"nReturned" : 1000000,
"executionTimeMillisEstimate" : 1339,
"works" : 1000001,
"advanced" : 1000000,
"needTime" : 0,
"needYield" : 0,
"saveState" : 7845,
"restoreState" : 7845,
"isEOF" : 1,
"invalidates" : 0,
"docsExamined" : 1000000,
"alreadyHasObj" : 0,
"inputStage" : {
"stage" : "IXSCAN",
"nReturned" : 1000000,
"executionTimeMillisEstimate" : 769,
"works" : 1000001,
"advanced" : 1000000,
"needTime" : 0,
"needYield" : 0,
"saveState" : 7845,
"restoreState" : 7845,
"isEOF" : 1,
"invalidates" : 0,
"keyPattern" : {
"created" : -1,
"name" : 1,
"age" : 1
},
"indexName" : "created_-1_name_1_age_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"created" : [ ],
"name" : [ ],
"age" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "backward",
"indexBounds" : {
"created" : [
"[MinKey, MaxKey]"
],
"name" : [
"[MaxKey, MinKey]"
],
"age" : [
"[MaxKey, MinKey]"
]
},
"keysExamined" : 1000000,
"seeks" : 1,
"dupsTested" : 0,
"dupsDropped" : 0,
"seenInvalidated" : 0
}
}
}
}

排序如果使用的是非前缀的局部字典排序,name需要保证前边的字段是等值筛选操作才行;

db.users.find({created:new Date("2021-10-30T08:17:01.184Z")}).sort({name:-1}).explain('executionStats')

{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.users",
"indexFilterSet" : false,
"parsedQuery" : {
"created" : {
"$eq" : ISODate("2021-10-30T08:17:01.184Z")
}
},
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"created" : -1,
"name" : 1,
"age" : 1
},
"indexName" : "created_-1_name_1_age_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"created" : [ ],
"name" : [ ],
"age" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "backward",
"indexBounds" : {
"created" : [
"[new Date(1635581821184), new Date(1635581821184)]"
],
"name" : [
"[MaxKey, MinKey]"
],
"age" : [
"[MaxKey, MinKey]"
]
}
}
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 0,
"executionTimeMillis" : 0,
"totalKeysExamined" : 0,
"totalDocsExamined" : 0,
"executionStages" : {
"stage" : "FETCH",
"nReturned" : 0,
"executionTimeMillisEstimate" : 0,
"works" : 1,
"advanced" : 0,
"needTime" : 0,
"needYield" : 0,
"saveState" : 0,
"restoreState" : 0,
"isEOF" : 1,
"invalidates" : 0,
"docsExamined" : 0,
"alreadyHasObj" : 0,
"inputStage" : {
"stage" : "IXSCAN",
"nReturned" : 0,
"executionTimeMillisEstimate" : 0,
"works" : 1,
"advanced" : 0,
"needTime" : 0,
"needYield" : 0,
"saveState" : 0,
"restoreState" : 0,
"isEOF" : 1,
"invalidates" : 0,
"keyPattern" : {
"created" : -1,
"name" : 1,
"age" : 1
},
"indexName" : "created_-1_name_1_age_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"created" : [ ],
"name" : [ ],
"age" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "backward",
"indexBounds" : {
"created" : [
"[new Date(1635581821184), new Date(1635581821184)]"
],
"name" : [
"[MaxKey, MinKey]"
],
"age" : [
"[MaxKey, MinKey]"
]
},
"keysExamined" : 0,
"seeks" : 1,
"dupsTested" : 0,
"dupsDropped" : 0,
"seenInvalidated" : 0
}
}
}
}

六、搜索数据对索引命中的影响

MongoDB对index的选择是受到实际场景的数据影响比较大的,即与实际数据的分布规律有关,也跟实际筛选出来的数据有关系;所以我们对索引的优化和测试都需要考虑实际的数据场景才行;

由于name的字段值筛选出来的key太多,不能充分利用index,所以MongoDB拒绝了name_1并选择了age_1;

db.users.find({
name:/^user/,
age:{$gte:110}
}).explain('executionStats') {
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.users",
"indexFilterSet" : false,
"parsedQuery" : {
"$and" : [
{
"age" : {
"$gte" : 110
}
},
{
"name" : {
"$regex" : "^user"
}
}
]
},
"winningPlan" : {
"stage" : "FETCH",
"filter" : {
"name" : {
"$regex" : "^user"
}
},
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"age" : 1
},
"indexName" : "age_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"age" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"age" : [
"[110.0, inf.0]"
]
}
}
},
"rejectedPlans" : [
{
"stage" : "FETCH",
"filter" : {
"age" : {
"$gte" : 110
}
},
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"name" : 1
},
"indexName" : "name_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"name" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"name" : [
"[\"user\", \"uses\")",
"[/^user/, /^user/]"
]
}
}
}
]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 83215,
"executionTimeMillis" : 246,
"totalKeysExamined" : 83215,
"totalDocsExamined" : 83215,
"executionStages" : {
"stage" : "FETCH",
"filter" : {
"name" : {
"$regex" : "^user"
}
},
"nReturned" : 83215,
"executionTimeMillisEstimate" : 232,
"works" : 83216,
"advanced" : 83215,
"needTime" : 0,
"needYield" : 0,
"saveState" : 658,
"restoreState" : 658,
"isEOF" : 1,
"invalidates" : 0,
"docsExamined" : 83215,
"alreadyHasObj" : 0,
"inputStage" : {
"stage" : "IXSCAN",
"nReturned" : 83215,
"executionTimeMillisEstimate" : 43,
"works" : 83216,
"advanced" : 83215,
"needTime" : 0,
"needYield" : 0,
"saveState" : 658,
"restoreState" : 658,
"isEOF" : 1,
"invalidates" : 0,
"keyPattern" : {
"age" : 1
},
"indexName" : "age_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"age" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"age" : [
"[110.0, inf.0]"
]
},
"keysExamined" : 83215,
"seeks" : 1,
"dupsTested" : 0,
"dupsDropped" : 0,
"seenInvalidated" : 0
}
}
}
}

我们修改一下name筛选条件的值,进一步缩小命中的范围,可以看到这次MongoDB选择了name_1;

db.users.find({
name:/^user8888/,
age:{$gte:110}
}).explain('executionStats') {
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.users",
"indexFilterSet" : false,
"parsedQuery" : {
"$and" : [
{
"age" : {
"$gte" : 110
}
},
{
"name" : {
"$regex" : "^user8888"
}
}
]
},
"winningPlan" : {
"stage" : "FETCH",
"filter" : {
"age" : {
"$gte" : 110
}
},
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"name" : 1
},
"indexName" : "name_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"name" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"name" : [
"[\"user8888\", \"user8889\")",
"[/^user8888/, /^user8888/]"
]
}
}
},
"rejectedPlans" : [
{
"stage" : "FETCH",
"filter" : {
"name" : {
"$regex" : "^user8888"
}
},
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"age" : 1
},
"indexName" : "age_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"age" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"age" : [
"[110.0, inf.0]"
]
}
}
}
]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 10,
"executionTimeMillis" : 0,
"totalKeysExamined" : 112,
"totalDocsExamined" : 111,
"executionStages" : {
"stage" : "FETCH",
"filter" : {
"age" : {
"$gte" : 110
}
},
"nReturned" : 10,
"executionTimeMillisEstimate" : 0,
"works" : 114,
"advanced" : 10,
"needTime" : 102,
"needYield" : 0,
"saveState" : 1,
"restoreState" : 1,
"isEOF" : 1,
"invalidates" : 0,
"docsExamined" : 111,
"alreadyHasObj" : 0,
"inputStage" : {
"stage" : "IXSCAN",
"nReturned" : 111,
"executionTimeMillisEstimate" : 0,
"works" : 113,
"advanced" : 111,
"needTime" : 1,
"needYield" : 0,
"saveState" : 1,
"restoreState" : 1,
"isEOF" : 1,
"invalidates" : 0,
"keyPattern" : {
"name" : 1
},
"indexName" : "name_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"name" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"name" : [
"[\"user8888\", \"user8889\")",
"[/^user8888/, /^user8888/]"
]
},
"keysExamined" : 112,
"seeks" : 2,
"dupsTested" : 0,
"dupsDropped" : 0,
"seenInvalidated" : 0
}
}
}
}

MongoDB之几种情况下的索引选择策略的更多相关文章

  1. oracle数据库什么情况下创建索引比较好

    索引就好象一本字典的目录.凭借字典的目录,我们可以非常迅速的找到我们所需要的条目.数据库也是如此.凭借Oracle数据库的索引,相关语句可以迅速的定位记录的位置,而不必去定位整个表. 虽 然说,在表中 ...

  2. 数据库表设计时一对一关系存在的必要性 数据库一对一、一对多、多对多设计 面试逻辑题3.31 sql server 查询某个表被哪些存储过程调用 DataTable根据字段去重 .Net Core Cors中间件解析 分析MySQL中哪些情况下数据库索引会失效

    数据库表设计时一对一关系存在的必要性 2017年07月24日 10:01:07 阅读数:694 在表设计过程中,我无意中觉得一对一关系觉得好没道理,直接放到一张表中不就可以了吗?真是说,网上信息什么都 ...

  3. 五种情况下会刷新控件状态(刷新所有子FWinControls的显示)——从DFM读取数据时、新增加子控件时、重新创建当前控件的句柄时、设置父控件时、显示状态被改变时

    五种情况下会刷新控件状态(刷新控件状态才能刷新所有子FWinControls的显示): 在TWinControls.PaintControls中,对所有FWinControls只是重绘了边框,而没有整 ...

  4. create-react-app:reject和不reject(使用react-app-rewired)这2种情况下的antd组件按需引入配置

    create-react-app:eject和不eject(使用react-app-rewired)这2种情况下的antd组件按需引入配置: 不eject(使用react-app-rewired)配置 ...

  5. Oracle redo 日志损坏的几种情况下的恢复

    Oracle redo 日志损坏的几种情况下的恢复 第一:损坏的redo为非正在使用的redo log 1.归档模式,不是当前正在日志损坏,数据库打开模式. 模拟损坏:SQL> select * ...

  6. Java实现PV操作 | 读者与写者(在三种情况下进行讨论)

    注 :本文应结合[天勤笔记]进行学习. 1.读者优先 设置rmutex信号量来对readcount变量进行互斥访问.mutex信号量对写者与读者进行同步. static syn rmutex=new ...

  7. Atitit.有分区情况下的表查询策略流程

    Atitit.有分区情况下的表查询策略流程 1. 分区表查询策略流程1 2. 常见数据库oracle mysql的分区查询语句1 2.1. 跨分区查询(oracle)1 2.2. 单分区查询 (ora ...

  8. varchar int 查询 到底什么情况下走索引?

    一个字符类型的.一个int类型的,查询的时候到底会不会走索引,其实很多工作了几年的开发人员有时也会晕,下面就用具体事例来测试一下. 1.  准备工作 先准备2张表,以备后续测试使用. 表1:创建表te ...

  9. virtualBox中有线和无线两种情况下centos虚拟机和本地机互ping的方案

    之前写微信点餐系统的时候,刚开始是无线连接,然后每次进去虚拟机ip和本地ip都会改变,所以每次都需要配置一下nginx,还有本地的路径.之后换有线连接,就研究了一下桥接模式有线情况下虚拟机静态ip设置 ...

随机推荐

  1. LuoguP2108 学英语 题解

    Content 给出整数 \(x\) 的英文写法,求出这个整数 \(x\). 规则详见题面. 数据范围:\(|x|\leqslant 999999999\)(\(9\) 个 \(9\)). Solut ...

  2. java 常用类库:Object类和Objects类

    1,Object类: Object类是所有的类,数组,枚举的父类,也就是说,JAVA中允许把任何的对象赋值给Object类(包括基础数据类型),当定义一个类的时候,没有使用extends关键字显示指定 ...

  3. redis集群搭建,使用注意

    https://www.cnblogs.com/vieta/p/11192137.html https://blog.csdn.net/qq_42815754/article/details/8291 ...

  4. Shell 丢弃错误和输出信息

    shell中使用>/dev/null 2>&1 丢弃信息   在一些Shell脚本中,特别是Crontab的脚本中,经常会看到 >/dev/null 2>&1这 ...

  5. Linux使用SCP命令不使用密钥直接进行远程复制(SSH免密登录)

    假设A服务器要把文件复制到B服务器上 首先我们要在A服务器上生成密钥对 参考:https://www.cnblogs.com/pxblog/p/14396409.html 然后在把生成的密钥公钥id_ ...

  6. -fno-rtti -fno-exceptions

    -fno-rtti 禁用运行时类型信息-fno-exceptions 禁用异常机制一般只有对程序运行效率及资源占用比较看重的场合才会使用, 如果要做这两个的话最好连libstdc++和其他所有的的c+ ...

  7. 【LeetCode】390. Elimination Game 解题报告(Python)

    [LeetCode]390. Elimination Game 解题报告(Python) 标签: LeetCode 题目地址:https://leetcode.com/problems/elimina ...

  8. A. Points on Line

    A. Points on Line time limit per test 2 seconds memory limit per test 256 megabytes input standard i ...

  9. C++单元测试框架gtest使用

    作用 作为代码编码人员,写完代码,不仅要保证编译通过和运行,还要保证逻辑尽量正确.单元测试是对软件可测试最小单元的检查和校验.单元测试与其他测试不同,单元测试可看作是编码工作的一部分,应该由程序员完成 ...

  10. [Error]ValueError: Object arrays cannot be loaded when allow_pickle=False

    问题描述使用numpy的函数 numpy.load() 加载数据时报错: ValueError: Object arrays cannot be loaded when allow_pickle=Fa ...