索引及explain
1)mongodb的_id是如何生成的?可以有什么用途?
ObjectId是一个12字节的 BSON 类型字符串。按照字节顺序,一次代表:
4字节:UNIX时间戳
3字节:表示运行MongoDB的主机
2字节:表示生成此_id的进程
3字节:由一个随机数开始的计数器生成的值
用途:主键总是有一个唯一索引,在分布式环境下我们通常有两种方式分配唯一id
而mongodb的主键生成策略,时间搓保证了数据库大致是按时间顺序,主机+进程号的组合保证不同机器生成的主键id一定不一样,最后的计数器保证了单台机器生成的主键的唯一,这样不同机器就可以并行执行,提高了并发量。
2)如何查看一次查询是否命中了索引?命中的是什么索引?如果索引命中情况不理想,如何指定使用某个索引进行查询?
根据totalKeysExamined和stage查看是否命中索引,indexName为命中索引的名称
如果索引命中情况不理想,如何指定使用某个索引进行查询?
3)聚合查询如何使用explain
db.collection.aggregate(pipeline, options)
其中options,有字段explain,设置为true即可
4)explain的一些关键字段,我会提问,到官网查查学习清楚,记录自己学习的成果总结。
explain有三种模式:
queryPlanner
explain.queryPlanner.indexFilterSet
针对该query是否有indexfilter
explain.queryPlanner.winningPlan
查询优化器针对该query所返回的最优执行计划的详细内容
explain.queryPlanner.winningPlan.stage
最优执行计划的stage
explain.queryPlanner.winningPlan.inputStage
explain.queryPlanner.winningPlan.stage的child stage
explain.queryPlanner.winningPlan.indexName
winning plan所选用的index。
explain.queryPlanner.winningPlan.isMultiKey
是否是Multikey,此处返回是false,如果索引建立在array上,此处将是true。
explain.queryPlanner.winningPlan.direction
此query的查询顺序,包括forward和backward
explain.queryPlanner.winningPlan.indexBounds
winningplan所扫描的索引范围
executionStats
nReturned
该条查询返回的条目
totalDocsExamined
文档扫描条目
totalKeysExamined
索引扫描条目
executionStats.executionTimeMillis
该query的整体查询时间
executionStats.executionStages.executionTimeMillis
该查询根据index去检索document获取具体数据的时间
executionStats.executionStages.inputStage.executionTimeMillis
该查询扫描index所用时间
executionStats.executionStages.stage
这里是扫描documents的stage
stage取值和意义:
值 | 意义 |
---|---|
COLLSCAN | 全表扫描 |
IXSCAN | 索引扫描 |
FETCH | 根据索引去检索指定document |
SHARD_MERGE | 将各个分片返回数据进行merge |
SORT | 表明在内存中进行了排序,没有使用到索引 |
LIMIT | 使用limit限制返回数 |
SKIP | 使用skip进行跳过 |
IDHACK | 针对id进行查询 |
SHARDING_FILTER | 通过mongos对分片数据进行查询 |
COUNT | 利用db.coll.explain().count()之类进行count运算 |
COUNTSCAN | count不使用Index进行count时的stage返回 |
COUNT_SCAN | count使用Index进行count时的stage返回 |
SUBPLA | 未使用到索引的$or查询的stage返回 |
TEXT | 使用全文索引进行查询时候的stage返回 |
PROJECTION | 限定返回字段时候stage的返回 |
最理想的状态是nReturned=totalKeysExamined & totalDocsExamined=0(覆盖索引,仅仅使用到了index,无需文档扫描)或者nReturned=totalKeysExamined=totalDocsExamined
如果有sort的时候,为了使得sort不在内存中进行,我们可以在保证nReturned=totalDocsExamined
的基础上,totalKeysExamined可以大于totalDocsExamined与nReturned,因为量级较大的时候内存排序非常消耗性能
实战
//插入10条测试数据
db.testindex.insertMany([ {"a" : 1, "b" : 1, "c" : 1 }, {"a" : 1, "b" : 2, "c" : 2 }, {"a" : 1, "b" : 3, "c" : 3 }, {"a" : 4, "b" : 2, "c" : 3 }, {"a" : 4, "b" : 2, "c" : 5 }, {"a" : 4, "b" : 2, "c" : 5 }, {"a" : 2, "b" : 1, "c" : 1 }, {"a" : 1, "b" : 9, "c" : 1 }, {"a" : 1, "b" : 9, "c" : 1 }, {"a" : 1, "b" : 9, "c" : 1 } ])
执行下列语句:
db.testindex.find({ a: 1, b: {$lt:3}, }) .sort({c: -1}) .explain("executionStats")
执行计划分析:
executionStats.nReturned=2,符合条件的返回了2条 executionStats.totalKeysExamined=0,没有使用索引 executionStats.totalDocsExamined=10,扫描了所有记录 winningPlan.stage为SORT,未使用索引的sort executionStages.inputStage.stage=SORT_KEY_GENERATOR 结论:没有索引时,进行了全表扫描,没有使用到index,在内存中sort
第二步:对排序字段c加一个索引,再次执行
db.testindex.createIndex({c:1})
executionStages.executionStages.inputStage.stage=IXSCAN,没有使用内存排序了 executionStats.totalKeysExamined和executionStats.totalDocsExamined都为10,扫描了所有记录 结论:仅仅对sort排序进行了优化
第三步:添加组合索引a,b,c
db.testindex.createIndex({a:1,b:1,c:1})
executionStats.nReturned=2.0 返回了2条 executionStats.totalKeysExamined=2.0 索引扫描了2条 executionStats.totalDocsExamined=2.0 文档扫描了2条 executionStats.executionStages.inputStage.stage=SORT 进行了非索引排序 结论:nReturned=totalDocsExamined=totalKeysExamined,符合我们的期望,但是在内存中进行了排序
第四步:添加组合索引a,c,b
db.testindex.createIndex({a:1,c:1,b:1})
executionStats.nReturned=2.0 返回了2条 executionStats.totalKeysExamined=4.0 索引扫描了4条 executionStats.totalDocsExamined=2.0 文档扫描了2条 executionStats.executionStages.inputStage.stage=IXSCAN 结论:虽然不是nReturned=totalKeysExamined=totalDocsExamined,但是Stage无Sort,即利用了index进行排序,而非内存,这个性能的提升高于多扫几个index的代价。
总结:当查询覆盖精确匹配,范围查询与排序的时候,
{精确匹配字段,排序字段,范围查询字段}这样的索引排序会更为高效。
5)mongodb索引的实现原理,是什么数据结构?为什么使用这种数据结构?
索引类型:
db.collection.createIndex({字段: 1/-1})
来创建单字段的升序/降序索引,MongoDB默认创建的id索引也是这种类型。索引额外属性:
_id
索引就是唯一索引MySQL 认为遍历数据的查询是常见的,它经常需要处理各个表之间的关系并通过范围查询一些数据,所以它选择 B+ 树作为底层数据结构,而舍弃了通过非叶节点存储数据这一特性。
MongoDB 认为查询单个数据记录远比遍历数据更加常见,由于 B 树的非叶结点也可以存储数据,所以查询一条数据所需要的平均随机 IO 次数会比 B+ 树少,使用 B 树的 MongoDB 在类似场景中的查询速度就会比 MySQL 快。但是 MongoDB 有时也需要遍历数据,所以它不能选择哈希表这种数据结构,因为虽然等值查询时间复杂度为O(1),但是范围查询是O(n),过于极端。
B 树既能降低查询一条数据所需要的平均随机 IO 次数,对遍历数据的查询也可以保证可以接受的时延。