不妨使用explian()来分析一下这个查询(仅保留executionStats):
db.events.explain("executionStats").count({"projectId" : ObjectId("58211791ea2640000c7a3fe6"),createAt:{"$lt" : ISODate("2018-02-05T20:30:00.073Z")}}) "executionStats": { "executionSuccess": true, "nReturned": 20853, "executionTimeMillis": 28055, "totalKeysExamined": 28338, "totalDocsExamined": 28338, "executionStages": { "stage": "FETCH", "filter": { "createAt": { "$lt": ISODate("2018-02-05T20:30:00.073Z") } }, "nReturned": 20853, "executionTimeMillisEstimate": 27815, "works": 28339, "advanced": 20853, "needTime": 7485, "needYield": 0, "saveState": 1387, "restoreState": 1387, "isEOF": 1, "invalidates": 0, "docsExamined": 28338, "alreadyHasObj": 0, "inputStage": { "stage": "IXSCAN", "nReturned": 28338, "executionTimeMillisEstimate": 30, "works": 28339, "advanced": 28338, "needTime": 0, "needYield": 0, "saveState": 1387, "restoreState": 1387, "isEOF": 1, "invalidates": 0, "keyPattern": { "projectId": 1 }, "indexName": "projectId_1", "isMultiKey": false, "isUnique": false, "isSparse": false, "isPartial": false, "indexVersion": 1, "direction": "forward", "indexBounds": { "projectId": [ "[ObjectId('58211791ea2640000c7a3fe6'), ObjectId('58211791ea2640000c7a3fe6')]" ] }, "keysExamined": 28338, "dupsTested": 0, "dupsDropped": 0, "seenInvalidated": 0 } } }
可知,events集合并没有为projectId与createAt建立复合索引,因此IXSCAN阶段采用的是projectId索引,其nReturned为28338; FETCH阶段需要根据createAt进行过滤,其nReturned为20853,过滤掉了7485个文档;另外,IXSCAN与FETCH阶段的executionTimeMillisEstimate分别为30ms和27815ms,因此基本上所有时间都消耗在了FETCH阶段,这应该是读取硬盘导致的。
创建复合索引
没有为projectId和createAt创建复合索引是个尴尬的错误,赶紧补救一下:
db.events.createIndex({projectId:1,createTime:-1},{background: true})
在生产环境构建索引这种事最好是晚上做,这个命令一共花了大概7个小时吧!background设为true,指的是不要阻塞数据库的其他操作,保证数据库的可用性。但是,这个命令会一直占用着终端,这时不能使用CTRL + C,否则会终止索引构建过程。
复合索引创建成果之后,前文的查询就快了很多(仅保留executionStats):
db.javascriptevents.explain("executionStats").count({"projectId" : ObjectId("58211791ea2640000c7a3fe6"),createAt:{"$lt" : ISODate("2018-02-05T20:30:00.073Z")}}) "executionStats": { "executionSuccess": true, "nReturned": 0, "executionTimeMillis": 47, "totalKeysExamined": 20854, "totalDocsExamined": 0, "executionStages": { "stage": "COUNT", "nReturned": 0, "executionTimeMillisEstimate": 50, "works": 20854, "advanced": 0, "needTime": 20853, "needYield": 0, "saveState": 162, "restoreState": 162, "isEOF": 1, "invalidates": 0, "nCounted": 20853, "nSkipped": 0, "inputStage": { "stage": "COUNT_SCAN", "nReturned": 20853, "executionTimeMillisEstimate": 50, "works": 20854, "advanced": 20853, "needTime": 0, "needYield": 0, "saveState": 162, "restoreState": 162, "isEOF": 1, "invalidates": 0, "keysExamined": 20854, "keyPattern": { "projectId": 1, "createAt": -1 }, "indexName": "projectId_1_createTime_-1", "isMultiKey": false, "isUnique": false, "isSparse": false, "isPartial": false, "indexVersion": 1 } } }
可知,count操作使用了projectId和createAt的复合索引,因此非常快,只花了46ms,性能提升了将近600倍!!!对比使用复合索引前后的结果,发现totalDocsExamined从28338降到了0,表示使用复合索引之后不再需要去查询文档,只需要扫描索引就好了,这样就不需要去访问磁盘了,自然快了很多。
参考
- MongoDB 复合索引
- MongoDB文档:Compound Indexes
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。