Go教程

Mongodb索引及explain

本文主要是介绍Mongodb索引及explain,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

索引及explain

1)mongodb的_id是如何生成的?可以有什么用途?

ObjectId是一个12字节的 BSON 类型字符串。按照字节顺序,一次代表:
4字节:UNIX时间戳
3字节:表示运行MongoDB的主机
2字节:表示生成此_id的进程
3字节:由一个随机数开始的计数器生成的值
用途:主键总是有一个唯一索引,在分布式环境下我们通常有两种方式分配唯一id

  1. 使用一个原子计数器,这个计算器每次返回一个id并加1,这个数字需要通过网络传输
  2. 在机器数量确定的情况下,把每台服务器的step设置为机器数量,这样可以生成全局唯一的id
    第一种方案由于计数器是原子的,并发量大时存在大量的锁冲突,网络传输也有开销
    第二种方案不利于拓展

而mongodb的主键生成策略,时间搓保证了数据库大致是按时间顺序,主机+进程号的组合保证不同机器生成的主键id一定不一样,最后的计数器保证了单台机器生成的主键的唯一,这样不同机器就可以并行执行,提高了并发量。

2)如何查看一次查询是否命中了索引?命中的是什么索引?如果索引命中情况不理想,如何指定使用某个索引进行查询?

根据totalKeysExamined和stage查看是否命中索引,indexName为命中索引的名称
如果索引命中情况不理想,如何指定使用某个索引进行查询?

  1. 根据实际情况删除被误选的索引
  2. 使用hint()传入一个特定的索引:
    假设在users上有个{"a": 1}的索引,名称是"a_1",则如下两种方式等价:
    db.users.find({}).hint({"a": 1)
    db.users.find({}).hint("a_1")

3)聚合查询如何使用explain

db.collection.aggregate(pipeline, options)
其中options,有字段explain,设置为true即可

4)explain的一些关键字段,我会提问,到官网查查学习清楚,记录自己学习的成果总结。

explain有三种模式:

  • queryPlanner:不会去真正进行query语句查询,而是针对query语句进行执行计划分析并选出winning plan
  • executionStats
  • allPlansExecution

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运算
COUNTSCANcount不使用Index进行count时的stage返回
COUNT_SCANcount使用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索引也是这种类型。
  • 复合索引:针对多个字段联合创建索引,先按第一个字段排序,第一个字段相同的文档按第二个字段排序,依次类推。
  • 多key索引:当索引的字段为数组时,创建出的索引称为多key索引,多key索引会为数组的每个元素建立一条索引。
  • 其他索引:哈希索引、地理位置索引、文本索引

索引额外属性:

  • 唯一索引:保证索引对应的字段不会出现相同的值,比如_id索引就是唯一索引
  • TTL索引:可以针对某个时间字段,指定文档的过期时间
  • 部分索引: 只针对符合某个特定条件的文档建立索引,3.2版本才支持该特性
  • 稀疏索引: 只针对存在索引字段的文档建立索引,可看做是部分索引的一种特殊情况

MySQL 认为遍历数据的查询是常见的,它经常需要处理各个表之间的关系并通过范围查询一些数据,所以它选择 B+ 树作为底层数据结构,而舍弃了通过非叶节点存储数据这一特性。

MongoDB 认为查询单个数据记录远比遍历数据更加常见,由于 B 树的非叶结点也可以存储数据,所以查询一条数据所需要的平均随机 IO 次数会比 B+ 树少,使用 B 树的 MongoDB 在类似场景中的查询速度就会比 MySQL 快。但是 MongoDB 有时也需要遍历数据,所以它不能选择哈希表这种数据结构,因为虽然等值查询时间复杂度为O(1),但是范围查询是O(n),过于极端。

B 树既能降低查询一条数据所需要的平均随机 IO 次数,对遍历数据的查询也可以保证可以接受的时延。

这篇关于Mongodb索引及explain的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!