1.索引的作用
- 线上业务一般存储了海量的数据,且业务读写比例往往是读的比例更高,索引可以加速查询效率。
- 对增删改有一定的影响,因为需要做索引的维护,维护树的平衡,且还存在索引碎片/索引重建问题,一般公司不会整理索引碎片。
- 数据量很少的时候全表扫描反而更快速,应该合理的建立索引可避免索引维护等影响写入性能,索引应建在经常搜索和排序的字段上。
2.索引的结构
- mongodb默认的wiredtiger使用的是b+tree算法。数据结构使用平衡多叉树,相对于b树更稳定更快,这个也是参考了关系型数据库的模型。
- 索引有自己单独的文件 index开头,末尾是wt(wiredtiger引擎),在成本可以承受下将数据和索引分开存储在不同磁盘上。
- 使用索引,将索引读取到内存中,尽量保持常驻内存可以避免去磁盘查找增加磁盘io。
- 多个索引下,mongodb会评估使用那个索引,也就是执行计划竞赛机制。
3.索引的类型和限制
索引类型
- 单列索引/联合索引 这两个最常用,
- 多建索引 数组类型的索引
- 地理索引 坐标 导航软件,大众点评使用此索引。
- 全文索引 单词集合 针对大量字符串内容的。一般用独立搜索引擎框架。
- 哈希索引 处理哈希字段
- 唯一索引 索引内容不可重复
- TTL索引 过期时间
- 稀疏索引 仅包含存在的字段
- 局部索引 按条件索引
mongodb索引限制(一般情况下不会超过)
- 集合中索引,个数不超过64,长度不超过125字符,符合不超过31个字段。
- 索引存储在内存中,确保索引不超过内存大小的限制。
- 注意当前版本是否支持你想要创建的索引类型。
4.查询计划竞赛(选择查询计划的机制)
stateDiagram-v2
执行查询语句 --> 没有执行计划
执行查询语句 --> 已有缓存的最优执行计划
没有执行计划 --> 优化器评估
已有缓存的最优执行计划 --> 后期多次调用同一个查询缓存计划
优化器评估 --> 生成多个执行计划
生成多个执行计划 --> 并行运行执行计划保留执行最快的计划
并行运行执行计划保留执行最快的计划 --> 后期多次调用同一个查询缓存计划
后期多次调用同一个查询缓存计划 --> 返回结果
后期多次调用同一个查询缓存计划 --> 修改数据或索引
修改数据或索引 --> 优化器评估
- 执行查询语句,没有执行计划优化器会评估,如果有缓存过执行计划,则执行计划返回结果。
- 优化器评估后生成多个执行,并行执行并缓存最优的执行计划。
- 后期同一个查询多次调用已经缓存的执行计划。
- 虽然有执行计划,但是修改了数据或索引,则优化器重新评估执行计划并缓存。
- 查询计划可以更新/删除,PlanCache.cleaer()
- db.collection.explain()/cursor.explain 返回查询计划和统计信息。
5.创建/删除索引
假设有name和age字段
db.collection.createIndex({name:1}) 创建单列索引
db.collection.createIndex({name:1,age:1}) 创建复合索引
db.collection.dropIndex({name:1}) 删除集合中指定索引
db.collection.dropIndexes() 删除集合中所有索引,不删除默认_id索引
db.collection.getIndexes() 获取集合中所有的索引
db.collection.totalIndexSize() 返回集合使用的总大小
不建议使用的
db.collection.reIndex() 重建集合中的索引
db.ins.ensureIndex({age:1,name:1}) 创建索引,可用因为是createIndex别名。
- db.collection.createIndex({name:1},{backgroud:true}) 4.2版本之前后台创建索引不阻塞主进程。4.2之后弃用,索引只在开始和结束才占用排它锁其余过程进行交叉的读写操作
- 删除了索引,查询机会重新评估,必须等待排他锁释放之后才会删除索引。
- 索引名字是否唯一,是否过期,是不是稀疏索引都可以不考虑。
6.explain/hint 查询计划/强制走特定索引
explain,有三种选择
- queryPlanner(默认,可以带false表示默认):使用queryPlanner只列出所有可能执行的方案,不会执行实际的语句,显示已经胜出的方案winningPlan。
- executionStats:使用executionStats只执行winningPlan方案,并输出结果。
- allPlansExecution(true):使用allPlansExecution执行所有的方案,并输出结果。
hint
- 可以使用hint来强制指定索引执行。db.collection.find({条件}).hint({你想使用的索引名称})
执行计划的解读
db.ins.find({money:39}).explain()
//执行计划竞赛胜利的执行计划。
"winningPlan" : {
"stage" : "FETCH", //匹配
"inputStage" : {
"stage" : "IXSCAN", //索引扫描 COLLSCAN为全表扫描
"keyPattern" : {
"money" : 1
},
"indexName" : "money_1", //使用的索引名称
"isMultiKey" : false,
"multiKeyPaths" : {
"money" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"money" : [
"[39.0, 39.0]" //最大值和最小值
]
}
}
},
"rejectedPlans" : [ //被抛弃的索引执行计划
{
"stage" : "FETCH", //匹配
"inputStage" : {
"stage" : "IXSCAN", //索引扫描
"keyPattern" : { //索引字段
"money" : 1,
"status" : 1
},
"indexName" : "money_1_status_1", //索引名称
"isMultiKey" : false,
"multiKeyPaths" : {
"money" : [ ],
"status" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"money" : [
"[39.0, 39.0]"
],
"status" : [
"[MinKey, MaxKey]"
]
......
......