性能优化
知道 index 和执行计划之后,我们要学会分析执行计划。选择出最优秀的索引方案。
索引准则
-
避免全表扫描
-
避免过多索引。如无必要,不增索引。
关注点
关注点1:执行时间
其中有3个executionTimeMillis,分别是
executionStats.executionTimeMillis
该query的整体查询时间
executionStats.executionStages.executionTimeMillis
该查询根据index去检索document获取2条具体数据的时间
executionStats.executionStages.inputStage.executionTimeMillis
该查询扫描 2 行index所用时间
这三个值我们都希望越少越好,那么是什么影响这这三个返回值呢?
抛开硬件因素等不谈,我们来进行下一层的剥离。
关注点2:返回的条数
这里主要谈3个返回项,nReturned,totalKeysExamined与totalDocsExamined,分别代表该条查询返回的条目、索引扫描条目和文档扫描条目。
很好理解,这些都直观的影响到executionTimeMillis,我们需要扫描的越少速度越快。
对于一个查询, 我们最理想的状态是
nReturned=totalKeysExamined & totalDocsExamined=0
(cover index,仅仅使用到了index,无需文档扫描,这是最理想状态。)
或者
nReturned=totalKeysExamined=totalDocsExamined
(需要具体情况具体分析)
(正常index利用,无多余index扫描与文档扫描。)
如果有sort的时候,为了使得sort不在内存中进行,我们可以在保证 nReturned=totalDocsExamined 的基础上,totalKeysExamined可以大于totalDocsExamined与nReturned,因为量级较大的时候内存排序非常消耗性能。
后面我们会针对例子来进行分析。
关注点3:Stage状态分析
那么又是什么影响到了totalKeysExamined与totalDocsExamined呢?
就是Stage的类型,Stage的具体含义在上文中有提及,如果认真看的同学就不难理解为何Stage会影响到totalKeysExamined 和totalDocsExamined从而影响executionTimeMillis了。
此前有讲解过stage的类型,这里不再赘述。
普通查询
- 希望看到
Fetch+IDHACK
Fetch+ixscan
Limit+(Fetch+ixscan)
PROJECTION+ixscan
SHARDING_FILTER+ixscan
...
- 不希望看到
COLLSCAN(全表扫),SORT(使用sort但是无index),不合理的SKIP,SUBPLA(未用到index的$or)
对于 count
- 希望看到的有
COUNT_SCAN
- 不希望看到的有
COUNTSCAN
Explain 实例
初始化测试数据
db.explain.insertMany([
{ \"_id\" : ObjectId(\"55b86d6bd7e3f4ccaaf20d70\"), \"a\" : 1, \"b\" : 1, \"c\" : 1 },
{ \"_id\" : ObjectId(\"55b86d6fd7e3f4ccaaf20d71\"), \"a\" : 1, \"b\" : 2, \"c\" : 2 },
{ \"_id\" : ObjectId(\"55b86d72d7e3f4ccaaf20d72\"), \"a\" : 1, \"b\" : 3, \"c\" : 3 },
{ \"_id\" : ObjectId(\"55b86d74d7e3f4ccaaf20d73\"), \"a\" : 4, \"b\" : 2, \"c\" : 3 },
{ \"_id\" : ObjectId(\"55b86d75d7e3f4ccaaf20d74\"), \"a\" : 4, \"b\" : 2, \"c\" : 5 },
{ \"_id\" : ObjectId(\"55b86d77d7e3f4ccaaf20d75\"), \"a\" : 4, \"b\" : 2, \"c\" : 5 },
{ \"_id\" : ObjectId(\"55b879b442bfd1a462bd8990\"), \"a\" : 2, \"b\" : 1, \"c\" : 1 },
{ \"_id\" : ObjectId(\"55b87fe842bfd1a462bd8991\"), \"a\" : 1, \"b\" : 9, \"c\" : 1 },
{ \"_id\" : ObjectId(\"55b87fe942bfd1a462bd8992\"), \"a\" : 1, \"b\" : 9, \"c\" : 1 },
{ \"_id\" : ObjectId(\"55b87fe942bfd1a462bd8993\"), \"a\" : 1, \"b\" : 9, \"c\" : 1 }
])
业务需求
查询 a=1,b 的值小于3,且按照 c 倒叙排序
db.d.find({a:1,b:{$lt:3}}).sort({c:-1})
没有索引的情况
db.explain.find({a:1,b:{$lt:3}}).sort({c:-1}).explain(\'executionStats\')
结果如下:
{
\"queryPlanner\" : {
\"plannerVersion\" : 1,
\"namespace\" : \"test.explain\",
\"indexFilterSet\" : false,
\"parsedQuery\" : {
\"$and\" : [
{
\"a\" : {
\"$eq\" : 1.0
}
},
{
\"b\" : {
\"$lt\" : 3.0
}
}
]
},
\"winningPlan\" : {
\"stage\" : \"SORT\",
\"sortPattern\" : {
\"c\" : -1.0
},
\"inputStage\" : {
\"stage\" : \"SORT_KEY_GENERATOR\",
\"inputStage\" : {
\"stage\" : \"COLLSCAN\",
\"filter\" : {
\"$and\" : [
{
\"a\" : {
\"$eq\" : 1.0
}
},
{
\"b\" : {
\"$lt\" : 3.0
}
}
]
},
\"direction\" : \"forward\"
}
}
},
\"rejectedPlans\" : []
},
\"executionStats\" : {
\"executionSuccess\" : true,
\"nReturned\" : 2,
\"executionTimeMillis\" : 0,
\"totalKeysExamined\" : 0,
\"totalDocsExamined\" : 10,
\"executionStages\" : {
\"stage\" : \"SORT\",
\"nReturned\" : 2,
\"executionTimeMillisEstimate\" : 0,
\"works\" : 16,
\"advanced\" : 2,
\"needTime\" : 13,
\"needYield\" : 0,
\"saveState\" : 0,
\"restoreState\" : 0,
\"isEOF\" : 1,
\"invalidates\" : 0,
\"sortPattern\" : {
\"c\" : -1.0
},
\"memUsage\" : 126,
\"memLimit\" : 33554432,
\"inputStage\" : {
\"stage\" : \"SORT_KEY_GENERATOR\",
\"nReturned\" : 2,
\"executionTimeMillisEstimate\" : 0,
\"works\" : 13,
\"advanced\" : 2,
\"needTime\" : 10,
\"needYield\" : 0,
\"saveState\" : 0,
\"restoreState\" : 0,
\"isEOF\" : 1,
\"invalidates\" : 0,
\"inputStage\" : {
\"stage\" : \"COLLSCAN\",
\"filter\" : {
\"$and\" : [
{
\"a\" : {
\"$eq\" : 1.0
}
},
{
\"b\" : {
\"$lt\" : 3.0
}
}
]
},
\"nReturned\" : 2,
\"executionTimeMillisEstimate\" : 0,
\"works\" : 12,
\"advanced\" : 2,
\"needTime\" : 9,
\"needYield\" : 0,
\"saveState\" : 0,
\"restoreState\" : 0,
\"isEOF\" : 1,
\"invalidates\" : 0,
\"direction\" : \"forward\",
\"docsExamined\" : 10
}
}
}
},
\"serverInfo\" : {
\"host\" : \"c120a2890d51\",
\"port\" : 27017,
\"version\" : \"4.0.4\",
\"gitVersion\" : \"f288a3bdf201007f3693c58e140056adf8b04839\"
},
\"ok\" : 1.0
}
此处的条数较少,暂时不关心时间。
我们开始看条数和 executionStats。
\"nReturned\" : 2, -- 符合的条件的返回为2条。
\"executionTimeMillis\" : 0,
\"totalKeysExamined\" : 0, -- 未命中索引
\"totalDocsExamined\" : 10, -- 扫描了所有记录
- 排序
executionStats.executionStages.sort=SORT 没有命中索引
- 内存占用
\"memUsage\" : 126, -- 占用的内存
\"memLimit\" : 33554432, -- 内存限制
- 扫描类型
executionStages.inputStage.stage=COLLSCAN,全表扫描,扫描条件为
\"filter\" : {
\"$and\" : [
{
\"a\" : {
\"$eq\" : 1.0
}
},
{
\"b\" : {
\"$lt\" : 3.0
}
}
]
}
优化步骤
排序字段优化
排序添加索引
db.explain.createIndex({c: 1})
查看执行计划
\"executionStats\" : {
\"executionSuccess\" : true,
\"nReturned\" : 2,
\"executionTimeMillis\" : 4,
\"totalKeysExamined\" : 10,
\"totalDocsExamined\" : 10,
\"executionStages\" : {
\"stage\" : \"FETCH\",
\"filter\" : {
\"$and\" : [
{
\"a\" : {
\"$eq\" : 1.0
}
},
{
\"b\" : {
\"$lt\" : 3.0
}
}
]
},
\"nReturned\" : 2,
\"executionTimeMillisEstimate\" : 0,
\"works\" : 11,
\"advanced\" : 2,
\"needTime\" : 8,
\"needYield\" : 0,
\"saveState\" : 0,
\"restoreState\" : 0,
\"isEOF\" : 1,
\"invalidates\" : 0,
\"docsExamined\" : 10,
\"alreadyHasObj\" : 0,
\"inputStage\" : {
\"stage\" : \"IXSCAN\",
\"nReturned\" : 10,
\"executionTimeMillisEstimate\" : 0,
\"works\" : 11,
\"advanced\" : 10,
\"needTime\" : 0,
\"needYield\" : 0,
\"saveState\" : 0,
\"restoreState\" : 0,
\"isEOF\" : 1,
\"invalidates\" : 0,
\"keyPattern\" : {
\"c\" : 1.0
},
\"indexName\" : \"c_1\",
\"isMultiKey\" : false,
\"multiKeyPaths\" : {
\"c\" : []
},
\"isUnique\" : false,
\"isSparse\" : false,
\"isPartial\" : false,
\"indexVersion\" : 2,
\"direction\" : \"backward\",
\"indexBounds\" : {
\"c\" : [
\"[MaxKey, MinKey]\"
]
},
\"keysExamined\" : 10,
\"seeks\" : 1,
\"dupsTested\" : 0,
\"dupsDropped\" : 0,
\"seenInvalidated\" : 0
}
}
}
结果分析
- 排序
Stage没有了SORT,因为我们sort字段有了index,但是由于查询还是没有index,故totalDocsExamined还是10,但是由于sort用了index,totalKeysExamined也是10
- 查询
还是扫描了所有的数据,需要进一步优化。
\"nReturned\" : 2,
\"executionTimeMillis\" : 4,
\"totalKeysExamined\" : 10,
\"totalDocsExamined\" : 10,
查询优化
新建索引
删除原来的索引。
db.explain.dropIndexes();
db.explain.createIndex({a:1, b:1, c:1});
执行计划
\"executionStats\" : {
\"executionSuccess\" : true,
\"nReturned\" : 2,
\"executionTimeMillis\" : 6,
\"totalKeysExamined\" : 2,
\"totalDocsExamined\" : 2,
\"executionStages\" : {
\"stage\" : \"SORT\",
\"nReturned\" : 2,
\"executionTimeMillisEstimate\" : 0,
\"works\" : 7,
\"advanced\" : 2,
\"needTime\" : 4,
\"needYield\" : 0,
\"saveState\" : 0,
\"restoreState\" : 0,
\"isEOF\" : 1,
\"invalidates\" : 0,
\"sortPattern\" : {
\"c\" : -1.0
},
\"memUsage\" : 126,
\"memLimit\" : 33554432,
\"inputStage\" : {
\"stage\" : \"SORT_KEY_GENERATOR\",
\"nReturned\" : 2,
\"executionTimeMillisEstimate\" : 0,
\"works\" : 4,
\"advanced\" : 2,
\"needTime\" : 1,
\"needYield\" : 0,
\"saveState\" : 0,
\"restoreState\" : 0,
\"isEOF\" : 1,
\"invalidates\" : 0,
\"inputStage\" : {
\"stage\" : \"FETCH\",
\"nReturned\" : 2,
\"executionTimeMillisEstimate\" : 0,
\"works\" : 3,
\"advanced\" : 2,
\"needTime\" : 0,
\"needYield\" : 0,
\"saveState\" : 0,
\"restoreState\" : 0,
\"isEOF\" : 1,
\"invalidates\" : 0,
\"docsExamined\" : 2,
\"alreadyHasObj\" : 0,
\"inputStage\" : {
\"stage\" : \"IXSCAN\",
\"nReturned\" : 2,
\"executionTimeMillisEstimate\" : 0,
\"works\" : 3,
\"advanced\" : 2,
\"needTime\" : 0,
\"needYield\" : 0,
\"saveState\" : 0,
\"restoreState\" : 0,
\"isEOF\" : 1,
\"invalidates\" : 0,
\"keyPattern\" : {
\"a\" : 1.0,
\"b\" : 1.0,
\"c\" : 1.0
},
\"indexName\" : \"a_1_b_1_c_1\",
\"isMultiKey\" : false,
\"multiKeyPaths\" : {
\"a\" : [],
\"b\" : [],
\"c\" : []
},
\"isUnique\" : false,
\"isSparse\" : false,
\"isPartial\" : false,
\"indexVersion\" : 2,
\"direction\" : \"forward\",
\"indexBounds\" : {
\"a\" : [
\"[1.0, 1.0]\"
],
\"b\" : [
\"[-inf.0, 3.0)\"
],
\"c\" : [
\"[MinKey, MaxKey]\"
]
},
\"keysExamined\" : 2,
\"seeks\" : 1,
\"dupsTested\" : 0,
\"dupsDropped\" : 0,
\"seenInvalidated\" : 0
}
}
}
}
}
结果分析
\"executionStats\" : {
\"executionSuccess\" : true,
\"nReturned\" : 2,
\"executionTimeMillis\" : 6,
\"totalKeysExamined\" : 2,
\"totalDocsExamined\" : 2,
\"executionStages\" : {
\"stage\" : \"SORT\",
- 查询
查询已经满足我们的条件了。
- 排序
但是排序为 SORT,属于内存排序,不符合我们的预期。
sort 优化方式1
新建索引
删除原来的索引。
db.explain.dropIndexes();
db.explain.createIndex({a:1, c:1, b:1});
执行计划
\"executionStats\" : {
\"executionSuccess\" : true,
\"nReturned\" : 2,
\"executionTimeMillis\" : 5,
\"totalKeysExamined\" : 4,
\"totalDocsExamined\" : 2,
\"executionStages\" : {
\"stage\" : \"FETCH\",
\"nReturned\" : 2,
\"executionTimeMillisEstimate\" : 0,
\"works\" : 5,
\"advanced\" : 2,
\"needTime\" : 2,
\"needYield\" : 0,
\"saveState\" : 0,
\"restoreState\" : 0,
\"isEOF\" : 1,
\"invalidates\" : 0,
\"docsExamined\" : 2,
\"alreadyHasObj\" : 0,
\"inputStage\" : {
\"stage\" : \"IXSCAN\",
\"nReturned\" : 2,
\"executionTimeMillisEstimate\" : 0,
\"works\" : 5,
\"advanced\" : 2,
\"needTime\" : 2,
\"needYield\" : 0,
\"saveState\" : 0,
\"restoreState\" : 0,
\"isEOF\" : 1,
\"invalidates\" : 0,
\"keyPattern\" : {
\"a\" : 1.0,
\"c\" : 1.0,
\"b\" : 1.0
},
\"indexName\" : \"a_1_c_1_b_1\",
\"isMultiKey\" : false,
\"multiKeyPaths\" : {
\"a\" : [],
\"c\" : [],
\"b\" : []
},
\"isUnique\" : false,
\"isSparse\" : false,
\"isPartial\" : false,
\"indexVersion\" : 2,
\"direction\" : \"backward\",
\"indexBounds\" : {
\"a\" : [
\"[1.0, 1.0]\"
],
\"c\" : [
\"[MaxKey, MinKey]\"
],
\"b\" : [
\"(3.0, -inf.0]\"
]
},
\"keysExamined\" : 4< 版权声明
本文仅代表作者观点,不代表百度立场。
本文系作者授权百度百家发表,未经许可,不得转载。




