本文的运行平台和是windows 7 64bit,Mongodb的版本是3.4,参考了MongoDB索引管理
组合索引的原理
数据库索引最容易理解的是单键索引,它用单独的一个字段建立索引,只要在查询语句中以有效的方式包含了该字段即可命中。
当索引中包含多个字段的时候,这个时候实际建立索引的原则其实和传统数据库例如Mysql类似,遵循最左前缀法则 即以最左边为起点任何连续的索引都能匹配上,且每匹配多一级就可能会减小一部分筛选范围。
例如存在一个索引为 a_1_b_1_c1,当我们查询 a、a+b 和 a+b+c 时都可以命中它,但b、b+c 和 c 则无法命中
获取MongoDB的执行计划
获得粗略的执行计划
Mongo 也为我们提供了 explain 方法用于分析一个语句的执行计划,通过执行计划我们可以了解到关于查询性能方面的很多信息,包括是否命中索引、如何命中索引、执行耗时、文档扫描数等,是性能优化最基本的分析手段
use HTCDR;
db.getCollection("HT_EXAM_REPORT").find({"jzlsh":"zy100000"}).explain();
输出:
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "HTCDR.HT_EXAM_REPORT",
"indexFilterSet" : false,
"parsedQuery" : {
"jzlsh" : {
"$eq" : "zy100000"
}
},
"winningPlan" : { //真正执行的查询计划
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN", //用到了索引,我们要避免出现 COLLSCAN字段(全表扫描)
"keyPattern" : {
"jzlsh" : 1,
"jgdm" : 1
},
"indexName" : "jzlsh_1_jgdm_1", //查询用到的索引名称
"isMultiKey" : false,
"multiKeyPaths" : {
"jzlsh" : [ ],
"jgdm" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"jzlsh" : [
"[\"zy100000\", \"zy100000\"]"
],
"jgdm" : [
"[MinKey, MaxKey]"
]
}
}
},
"rejectedPlans" : [ ] //拒绝的查询计划
},
"serverInfo" : {
"host" : "iZ23gt4a82qZ",
"port" : 27017,
"version" : "3.4.10-68-gf1f3809",
"gitVersion" : "f1f38099c3c964cc445f4805de0ce072b436e5cc"
},
"ok" : 1
}
Mongo 会通过优化分析选择其中一种更好的方案放置到 winningPlan,最终的执行计划是 winningPlan 所描述的方式。
其它稍次的方案则会被放置到 rejectedPlans 中
如果只想关注获胜的查询计划,可以通过以下命令直接过滤出获胜的查询计划
db.getCollection("HT_EXAM_REPORT").find({"jzlsh":"zy100000"}).explain().queryPlanner.winningPlan;
输出:
{
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"jzlsh" : 1,
"jgdm" : 1
},
"indexName" : "jzlsh_1_jgdm_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"jzlsh" : [ ],
"jgdm" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"jzlsh" : [
"[\"zy100000\", \"zy100000\"]"
],
"jgdm" : [
"[MinKey, MaxKey]"
]
}
}
}
获取查询的执行阶段
一个查询总执行流程分为若干个 stage(阶段),一个 stage 的分析基础可以是其它 stage 的输出结果,比如扫描索引IXSCAN(索引扫描),然后根据索引记录的地址,去磁盘中获取实际记录( FETCH 的方式提取到各个位置所对应的完整文档);又或者没有用上索引而开始全表扫描(Collection Scan)
- nReturned:执行返回的文档数
- executionTimeMillis: 执行时间(ms)
- totalKeysExamined:索引扫描条数
- totalDocsExamined:文档扫描条数
- executionStages:执行步骤
db.getCollection("HT_EXAM_REPORT").find({ "jzlsh": "zy100000" }).explain('executionStats').executionStats
输出:
{
"executionSuccess" : true,
"nReturned" : 25,
"executionTimeMillis" : 0,
"totalKeysExamined" : 25,
"totalDocsExamined" : 25,
"executionStages" : {
"stage" : "FETCH",
"nReturned" : 25,
"executionTimeMillisEstimate" : 0,
"works" : 26,
"advanced" : 25,
"needTime" : 0,
"needYield" : 0,
"saveState" : 0,
"restoreState" : 0,
"isEOF" : 1,
"invalidates" : 0,
"docsExamined" : 25,
"alreadyHasObj" : 0,
"inputStage" : {
"stage" : "IXSCAN",
"nReturned" : 25,
"executionTimeMillisEstimate" : 0,
"works" : 26,
"advanced" : 25,
"needTime" : 0,
"needYield" : 0,
"saveState" : 0,
"restoreState" : 0,
"isEOF" : 1,
"invalidates" : 0,
"keyPattern" : {
"jzlsh" : 1,
"jgdm" : 1
},
"indexName" : "jzlsh_1_jgdm_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"jzlsh" : [ ],
"jgdm" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"jzlsh" : [
"[\"zy100000\", \"zy100000\"]"
],
"jgdm" : [
"[MinKey, MaxKey]"
]
},
"keysExamined" : 25,
"seeks" : 1,
"dupsTested" : 0,
"dupsDropped" : 0,
"seenInvalidated" : 0
}
}
}
executionStages 这个字段的查看顺序是从内而外的,即实际先执行的是子 stage,再往上逐级执行父 stage,上面的例子就是先IXSCAN
扫描索引jzlsh_1_jgdm_1,然后根据索引上的地址去磁盘去获取完整的记录(FETCH)
Stage的类型
参考MongoDB干货系列2-MongoDB执行计划分析详解(2),Stage有以下类型,其对应意思是
Stage名称 | Stage含义 |
---|---|
COLLSCAN | 全表扫描 |
IXSCAN | 索引扫描 |
FETCH | 根据索引去检索指定document |
SHARD_MERGE | 将各个分片返回数据进行merge |
SORT | 表明在内存中进行了排序(与老版本的scanAndOrder:true一致) |
LIMIT | 使用limit限制返回数 |
SKIP | 使用skip进行跳过 |
IDHACK | 针对_id进行查询 |
COUNT | 利用db.coll.explain().count()之类进行count运算 |
COUNTSCAN | count不使用用Index进行count时的stage返回 |
COUNT_SCAN | count使用了Index进行count时的stage返回 |
SUBPLA | 未使用到索引的$or查询的stage返回 |
TEXT | 使用全文索引进行查询时候的stage返回 |
PROJECTION | 限定返回字段时候stage的返回 |
不希望看到包含如下的stage:
COLLSCAN(全表扫),SORT(使用sort但是无index),不合理的SKIP,SUBPLA(未用到index的$or)
对于count查询,希望看到的有:
COUNT_SCAN
不希望看到的有:
COUNTSCAN
参考 MongoDB干货系列2-MongoDB执行计划分析详解(3)博客中有一个优化查询的例子,讲的非常好
建立索引的语法
详情参考 Mongodb官方手册
db.collection.createIndex(keys, options)
keys:是一个文档 描述在哪些字段上建立索引
options: 可选的,控制索引建立的细节
options 有以下的参数
option参数名称 | options参数含义 |
---|---|
background | 布尔类型,可选的。 true代表在后台构建索引,不会阻止其他数据库在该集合上的活动,默认值为false。 |
unique | 布尔类型,可选的, 创建唯一索引 |
name | 字符串类型,可选的, 指定创建的索引名称 |
partialFilterExpression | document类型.如果指定,MongoDB只会给满足过滤表达式的记录建立索引. |
sparse | 布尔类型,可选的, 文档中不存在的字段数据不启用索引。默认值是 false |
expireAfterSeconds | integer,指定索引的过期时间 |
unique | 布尔类型,可选的, 创建唯一索引 |
创建索引的语句,常用
给user集合建正序索引:
db.users.createIndex({"name":1})
给name字段创建倒序索引:
db.users.createIndex({"name":-1})
给name,age字段创建组合索引
db.users.createIndex({"name":1,"age":1})
在后台给age字段创建索引(推荐加上生产环境加上background参数)
db.users.createIndex({"age":1},{background:1})
在前台创建索引期间会锁定数据库,会导致其它操作无法进行数据读写,在后台创建索引是,会定期释放写锁,从而保证其它操作的运行,但是后台操作会在耗时更长,尤其是在频繁进行写入的服务器上。
查看索引
getIndexes()方法可以用来查看集合的所有索引
totalIndexSize()查看集合索引的总大小
db.HT_EXAM_REPORT.getIndexes();
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "HTCDR.HT_EXAM_REPORT"
},
{
"v" : 2,
"key" : {
"jzlsh" : 1,
"jgdm" : 1
},
"name" : "jzlsh_1_jgdm_1",
"ns" : "HTCDR.HT_EXAM_REPORT"
},
{
"v" : 2,
"key" : {
"sfzh" : 1,
"bgsj" : -1,
"mzzybz" : 1,
"jgdm" : 1,
"xgbz" : 1,
"jgmc" : 1
},
"name" : "sfzh_1_bgsj_-1_mzzybz_1_jgdm_1_xgbz_1_jgmc_1",
"ns" : "HTCDR.HT_EXAM_REPORT"
}
]
db.HT_EXAM_REPORT.totalIndexSize();
输出:
348160
删除索引
不再需要的索引,我们可以将其删除,mongodb提供两种删除索引的方法:
dropIndex()方法用于删除指定的索引
dropIndexes()方法用于删除全部的索引
删除了user集合上 名称为name_1的索引
db.users.dropIndex("name_1")
删除user表上所有的索引(_id索引除外)
db.users.dropIndexes();