MongoDB复合索引详解

摘要: 对于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,表示使用复合索引之后不再需要去查询文档,只需要扫描索引就好了,这样就不需要去访问磁盘了,自然快了很多。

参考

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

原文地址:https://www.cnblogs.com/fundebug/p/8633886.html

时间: 2024-11-05 02:38:06

MongoDB复合索引详解的相关文章

mysql索引详解,摘自《MySQL 5权威指南》

本文介绍了数据库索引,及其优.缺点.针对MySQL索引的特点.应用进行了详细的描述.分析了如何避免MySQL无法使用,如何使用EXPLAIN分析查询语句,如何优化MySQL索引的应用.本文摘自<MySQL 5权威指南>(3rd)的8.9节.(2007.07.05最后更新)        索引是一种特殊的文件(InnoDB数据表上的索引是表空间的一个组成部分),它们包含着对数据表里所有记录的引用指针.注:[1]索引不是万能的!索引可以加快数据检索操作,但会使数据修改操作变慢.每修改数据记录,索引

mysql 联合索引详解

mysql 联合索引详解   联合索引又叫复合索引.对于复合索引:Mysql从左到右的使用索引中的字段,一个查询可以只使用索引中的一部份,但只能是最左侧部分.例如索引是key index (a,b,c). 可以支持a | a,b| a,b,c 3种组合进行查找,但不支持 b,c进行查找 .当最左侧字段是常量引用时,索引就十分有效. 两个或更多个列上的索引被称作复合索引.利用索引中的附加列,您可以缩小搜索的范围,但使用一个具有两列的索引 不同于使用两个单独的索引.复合索引的结构与电话簿类似,人名由

mysql 索引 详解

索引是快速搜索的关键.MySQL索引的建立对于MySQL的高效运行是很重要的.下面介绍几种常见的MySQL索引类型. 在数据库表中,对字段建立索引可以大大提高查询速度.假如我们创建了一个 mytable表: CREATE TABLE mytable(   ID INT NOT NULL,    username VARCHAR(16) NOT NULL  );   我们随机向里面插入了10000条记录,其中有一条:5555, admin. 在查找username="admin"的记录

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

最近为了解决IM消息记录的高速度写入.多文档类型支持的需求,决定使用MongoDB来解决. 考虑到MongoDB对VS版本要求较高,与我现有的VS版本不兼容,在leveldb.ssdb.redis.hbase等NoSQL中转了一圈,最后还是选择了MongoDB,应了那句话:没有最好的,只有最合适的. MongoDB由于使用了C++的新特性,官方建议使用VS2013来编译,最低要求VS2010. MongoDB C++驱动编译过程较为复杂,官方也没有提供编译好的驱动包,网上的资料编译版本都比较老了

Oracle索引详解

Oracle索引详解(一) ### --索引介绍 ??索引对于Oracle学习来说,非常重要,在数据量巨大的状况下,使用恰到好处的索引,将会使得数据查询时间大大减少,于2017/12/25暂时对Oracle中的索引进行一个大致的了解. 索引的创建语法 索引的特点 索引的不足 比较适合建立索引的列的特点 不适合建立索引的列的特点 限制索引(建立了索引,但是无法使用) 查询索引 组合索引 Oracle rowid 选择性 群集因子 二元高度 快速全表扫描 跳跃式扫描 索引的创建语法 create o

mysql联合索引详解

所有的MySQL列类型能被索引.在相关的列上的使用索引是改进SELECT操作性能的最好方法. 一.前缀索引 对于CHAR和VARCHAR列,你可以索引列的前缀.这更快并且比索引整个列需要较少的磁盘空间.在CREATE TABLE语句中索引列前缀的语法看起来像这样: KEY index_name (col_name(length))下面的例子为name列的头10个字符创建一个索引: mysql> CREATE TABLE test (name CHAR(200) NOT NULL,KEY inde

elasticsearch系列二:索引详解(快速入门、索引管理、映射详解、索引别名)

一.快速入门 1. 查看集群的健康状况 http://localhost:9200/_cat http://localhost:9200/_cat/health?v 说明:v是用来要求在结果中返回表头 状态值说明 Green - everything is good (cluster is fully functional),即最佳状态Yellow - all data is available but some replicas are not yet allocated (cluster i

elasticsearch系列三:索引详解(分词器、文档管理、路由详解)

一.分词器 1. 认识分词器  1.1 Analyzer   分析器 在ES中一个Analyzer 由下面三种组件组合而成: character filter :字符过滤器,对文本进行字符过滤处理,如处理文本中的html标签字符.处理完后再交给tokenizer进行分词.一个analyzer中可包含0个或多个字符过滤器,多个按配置顺序依次进行处理. tokenizer:分词器,对文本进行分词.一个analyzer必需且只可包含一个tokenizer. token filter:词项过滤器,对to

MySQL 索引详解大全

1.索引 索引是表的目录,在查找内容之前可以先在目录中查找索引位置,以此快速定位查询数据.对于索引,会保存在额外的文件中. 2. 索引,是数据库中专门用于帮助用户快速查询数据的一种数据结构.类似于字典中的目录,查找字典内容时可以根据目录查找到数据的存放位置,然后直接获取即可. 索引由数据库中一列或多列组合而成,其作用是提高对表中数据的查询速度索引的优点是可以提高检索数据的速度索引的缺点是创建和维护索引需要耗费时间索引可以提高查询速度,会减慢写入速度 索引分类 1.普通索引    2.唯一索引