摘要: 对于MongoDB的多键查询,创建复合索引可以有效提高性能。

什么是复合索引?

复合索引,即Compound Index,指的是将多个键组合到一起创建索引,这样可以加速匹配多个键的查询。不妨通过一个简单的示例理解复合索引。

students集合如下:

db.students.find().pretty()
{"_id" : ObjectId("5aa7390ca5be7272a99b042a"),"name" : "zhang","age" : "15"
}
{"_id" : ObjectId("5aa7393ba5be7272a99b042b"),"name" : "wang","age" : "15"
}
{"_id" : ObjectId("5aa7393ba5be7272a99b042c"),"name" : "zhang","age" : "14"
}

在name和age两个键分别创建了索引(_id自带索引):

db.students.getIndexes()
[{"v" : 1,"key" : {"name" : 1},"name" : "name_1","ns" : "test.students"},{"v" : 1,"key" : {"age" : 1},"name" : "age_1","ns" : "test.students"}
]

当进行多键查询时,可以通过explian()分析执行情况(结果仅保留winningPlan):

db.students.find({name:"zhang",age:"14"}).explain()
"winningPlan":
{"stage": "FETCH","filter":{"name":{"$eq": "zhang"}},"inputStage":{"stage": "IXSCAN","keyPattern":{"age": 1},"indexName": "age_1","isMultiKey": false,"isUnique": false,"isSparse": false,"isPartial": false,"indexVersion": 1,"direction": "forward","indexBounds":{"age": ["[\"14\", \"14\"]"]}}
}

由winningPlan可知,这个查询依次分为IXSCANFETCH两个阶段。IXSCAN即索引扫描,使用的是age索引;FETCH即根据索引去查询文档,查询的时候需要使用name进行过滤。

为name和age创建复合索引:

db.students.createIndex({name:1,age:1})db.students.getIndexes()
[{"v" : 1,"key" : {"name" : 1,"age" : 1},"name" : "name_1_age_1","ns" : "test.students"}
]

有了复合索引之后,同一个查询的执行方式就不同了:

db.students.find({name:"zhang",age:"14"}).explain()
"winningPlan":
{"stage": "FETCH","inputStage":{"stage": "IXSCAN","keyPattern":{"name": 1,"age": 1},"indexName": "name_1_age_1","isMultiKey": false,"isUnique": false,"isSparse": false,"isPartial": false,"indexVersion": 1,"direction": "forward","indexBounds":{"name": ["[\"zhang\", \"zhang\"]"],"age": ["[\"14\", \"14\"]"]}}
}

由winningPlan可知,这个查询的顺序没有变化,依次分为IXSCANFETCH两个阶段。但是,IXSCAN使用的是name与age的复合索引;FETCH即根据索引去查询文档,不需要过滤。

这个示例的数据量太小,并不能看出什么问题。但是实际上,当数据量很大,IXSCAN返回的索引比较多时,FETCH时进行过滤将非常耗时。接下来将介绍一个真实的案例。

定位MongoDB性能问题

随着接收的错误数据不断增加,我们Fundebug已经累计处理3.5亿错误事件,这给我们的服务不断带来性能方面的挑战,尤其对于MongoDB集群来说。

对于生产数据库,配置profile,可以记录MongoDB的性能数据。执行以下命令,则所有超过1s的数据库读写操作都会被记录下来。

db.setProfilingLevel(1,1000)

查询profile所记录的数据,会发现events集合的某个查询非常慢:

db.system.profile.find().pretty()
{"op" : "command","ns" : "fundebug.events","command" : {"count" : "events","query" : {"createAt" : {"$lt" : ISODate("2018-02-05T20:30:00.073Z")},"projectId" : ObjectId("58211791ea2640000c7a3fe6")}},"keyUpdates" : 0,"writeConflicts" : 0,"numYield" : 1414,"locks" : {"Global" : {"acquireCount" : {"r" : NumberLong(2830)}},"Database" : {"acquireCount" : {"r" : NumberLong(1415)}},"Collection" : {"acquireCount" : {"r" : NumberLong(1415)}}},"responseLength" : 62,"protocol" : "op_query","millis" : 28521,"execStats" : {},"ts" : ISODate("2018-03-07T20:30:59.440Z"),"client" : "192.168.59.226","allUsers" : [ ],"user" : ""
}

events集合中有数亿个文档,因此count操作比较慢也不算太意外。根据profile数据,这个查询耗时28.5s,时间长得有点离谱。另外,numYield高达1414,这应该就是操作如此之慢的直接原因。根据MongoDB文档,numYield的含义是这样的:

The number of times the operation yielded to allow other operations to complete. Typically, operations yield when they need access to data that MongoDB has not yet fully read into memory. This allows other operations that have data in memory to complete while MongoDB reads in data for the yielding operation.

这就意味着大量时间消耗在读取硬盘上,且读了非常多次。可以推测,应该是索引的问题导致的。

不妨使用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分别为30ms27815ms,因此基本上所有时间都消耗在了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

版权声明:
转载时请注明作者Fundebug以及本文地址:
https://blog.fundebug.com/2018/03/15/mongdb_compound_index_detail/

MongoDB复合索引详解相关推荐

  1. mongo 唯一约束索引_快速掌握mongoDB(三)——mongoDB的索引详解

    1 mongoDB索引的管理 本节介绍mongoDB中的索引,熟悉mysql/sqlserver等关系型数据库的小伙伴应该都知道索引对优化数据查询的重要性.我们先简单了解一下索引:索引的本质就是一个排 ...

  2. mysql联合索引(复合索引)详解

    这是一篇转自 itlab.idcquan.com的文章,原文地址:http://itlab.idcquan.com/linux/MYSQL/925211.html 联合索引又叫复合索引.对于复合索引: ...

  3. MySQL 联合索引(复合索引)详解

    转自:https://www.cnblogs.com/joyber/p/4349604.html 联合索引又叫复合索引.对于复合索引:Mysql从左到右的使用索引中的字段,一个查询可以只使用索引中的一 ...

  4. mysql 复合索引详解

    联合索引又叫复合索引.对于复合索引:Mysql从左到右的使用索引中的字段,一个查询可以只使用索引中的一部份,但只能是最左侧部分.例如索引是key index (a,b,c). 可以支持a | a,b| ...

  5. mysql 复合索引_mysql联合索引(复合索引)详解

    联合索引又叫复合索引.对于复合索引:Mysql从左到右的使用索引中的字段,一个查询可以只使用索引中的一部份,但只能是最左侧部分.例如索引是key index (a,b,c). 可以支持a | a,b| ...

  6. mysql 联合索引详解

    mysql 联合索引详解 联合索引又叫复合索引.对于复合索引:Mysql从左到右的使用索引中的字段,一个查询可以只使用索引中的一部份,但只能是最左侧部分.例如索引是key index (a,b,c). ...

  7. 使用VS2010编译MongoDB C++驱动详解

    最近为了解决IM消息记录的高速度写入.多文档类型支持的需求,决定使用MongoDB来解决. 考虑到MongoDB对VS版本要求较高,与我现有的VS版本不兼容,在leveldb.ssdb.redis.h ...

  8. oracle数据库中索值,Oracle数据库中的索引详解

    Oracle数据库中的索引详解以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧! 一 ROWID的概念 存储了row在数据文 ...

  9. MS SQL Server:分区表、分区索引详解

    MS SQL Server:分区表.分区索引 详解 1. 分区表简介 使用分区表的主要目的,是为了改善大型表以及具有各种访问模式的表的可伸缩性和可管理性.  大型表:数据量巨大的表.  访问模式: ...

最新文章

  1. PHP Web 2.0开发实战
  2. tps 数据库写并发衡量_MPP数据库简介
  3. [原创].使用Nios II 9.1中的Flash Programmer无法固化程序到EPCS上
  4. java 特殊符号正则_java利用正则表达式处理特殊字符的方法实例
  5. B端可视化: 图表设计(2)
  6. Android(java)学习笔记155:中文乱码的问题处理(qq登录案例)
  7. 父元素浮动子元素会浮动吗_为什么quot;overflow:hiddenquot;能清除浮动的影响
  8. 相分离在聚集多价信号蛋白过程中的作用Phase transitions in the assembly of multivalent signalling proteins
  9. 完成一个Laravel项目的过程
  10. Atitit Mysql查询优化器 存取类型 范围存取类型 索引存取类型 AND or的分析
  11. 计算机安装硬盘后无法启动不了,电脑一键装机后无法启动 电脑一键装机后无法启动解决办法详解...
  12. 为了寻找当下最好的照片备份方式,我写了7000字的长文...
  13. 关于MATLAB中使用latex语法
  14. 计算机病毒是如何入侵你的电脑吗,怎么样正确处理被病毒侵入的电脑
  15. Discuz!开发之模板制作CSS扩展规范与语法规范
  16. 知识点滴 - 有关剧本的网站
  17. android8 三星a9,三星GalaxyA9评测 已经远远超出中端手机的水平
  18. PXE+pxelinux+binlsrv+tftpd32远程安装windows 2003及心得
  19. java丐帮_Java多线程学习笔记(一)
  20. Numeric Keypad

热门文章

  1. SSRS报表连接超时的问题
  2. 关于 0xCCCCCCCC
  3. [leetcode]Sort Colors
  4. [LeetCode]: 96: Unique Binary Search Trees
  5. 2013年中国手机打车应用市场研究报告
  6. JavaScript判断浏览器类型及版本
  7. java枚举变量反解析用法
  8. ViewPager一屏显示多个item,及边缘滑动事件优化
  9. 《智能家居产品 从设计到运营》——第2章 技术搭台——与智能家居相关的技术...
  10. 关于域的的一些遐想(一)