数据库
首页 > 数据库> > MongoDB索引说明及慢查询优化

MongoDB索引说明及慢查询优化

作者:互联网

文章目录

索引种类

索引通常能够极大的提高查询的效率,如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录,即会进行全集合扫描

索引主要用于排序和检索

单键索引(常用)

在某一个特定的属性上建立索引,例如:db.users.createIndex({age:-1});

  • mongoDB在ID上建立了唯一的单键索引,所以经常会使用id来进行查询;
  • 在索引字段上进行精确匹配、排序以及范围查找都会使用此索引;

在这里插入图片描述
索引是存储在内存中的,上图说明 {score:1}索引在内存中是按照递增存储的

在查询的时候会先根据score定位到具体的文档


复合索引(常用)

在多个特定的属性上建立索引,例如:db.users.createIndex({username:1,age:-1,country:1});

  • 复合索引键的排序顺序,可以确定该索引是否可以支持排序操作;
  • 在索引字段上进行精确匹配、排序以及范围查找都会使用此索引,但与索引的顺序有关;
  • 为了性能考虑,应删除存在与复合索引中第一个键相同的单键索引,因为重复了

在这里插入图片描述
上图说明,复合索引 {userid:1,score:-1} 在内存中是按照userid升序、score降序进行存储的,即复合索引在内存中是有顺序的

因此在使用复合索引查询的时候一定要注意顺序,如果查询条件的顺序不一致是不会走索引的


多键索引

在数组的属性上建立索引,例如:db.users. createIndex({favorites.city:1})

针对这个数组的任意值的查询都会定位到这个文档,即多个索引入口或者键值引用同一个文档


哈希索引

不同于传统的B-树索引,哈希索引使用hash函数来创建索引。

例如:db.users. createIndex({username : ‘hashed’})

  • 在索引字段上进行精确匹配,但不支持范围查询,不支持多键hash;
  • Hash索引上的入口是均匀分布的,在分片集合中非常有用;

索引语法

MongoDB使用 ensureIndex() 方法来创建索引

ensureIndex()方法基本语法格式如下所示: db.collection.createIndex(keys, options)

  • 语法中 Key 值为要创建的索引字段,1为指定按升序创建索引,如果你想按降序来创建索引指定为-1,也可以指定为hashed(哈希索引)。
  • 语法中options为索引的属性,属性说明见下表

在这里插入图片描述
创建索引

  • 单键唯一索引:db.users.createIndex({username :1},{unique:true});
  • 单键唯一稀疏索引:db.users.createIndex({username :1},{unique:true,sparse:true});
  • 复合唯一稀疏索引:db.users.createIndex({username:1,age:-1},{unique:true,sparse:true});
  • 创建哈希索引并后台运行:db.users.createIndex({username :‘hashed’},{background:true});

删除索引

  • 根据索引名字删除某一个指定索引:db.users.dropIndex(“username_1”);
  • 删除某集合上所有索引:db.users.dropIndexs();
  • 重建某集合上所有索引:db.users.reIndex();
  • 查询集合上所有索引:db.users.getIndexes();

慢查询优化

第一步:找出慢查询语句

首先开启内置的查询分析器,记录读写操作效率

db.setProfilingLevel(n,{m}),n的取值可选0,1,2

  • 0是默认值表示不记录;
  • 1表示记录慢速操作,如果值为1,m必须赋值单位为ms,用于定义慢速查询时间的阈值(一般用这个)
  • 2表示记录所有的读写操作; 例如:db.setProfilingLevel(1,300)

然后查询监控结果,监控结果保存在一个特殊的盖子集合system.profile里,这个集合分配了128kb的空间,要确保监控分析数据不会消耗太多的系统性资源

盖子集合维护了自然的插入顺序,可以使用 $natural 操作符进行排序,如:db.system.profile.find().sort({’$natural’:-1}).limit(5)

盖子集合system.profile说明:

  • 大小或者数量固定;
  • 不能做update和delete操作;
  • 容量满了以后,按照时间顺序,新文档会覆盖旧文档

第二步:分析慢查询

找出慢速查询的原因比较棘手,原因可能有多个:应用程序设计不合理、不正确的数据模型、硬件配置问题,缺少索引等;接下来对于缺少索引的情况进行分析

使用 explain 分析慢查询,例如:

db.orders.find({"useCode":"james", "orderTime" : 
               { "$lt" : new Date("2015-04-03T16:00:00.000Z")}}).explain('executionStats')

explain的入参可选值为:

  • “queryPlanner” 是默认值,表示仅仅展示执行计划信息
  • “executionStats” 表示展示执行计划信息同时展示被选中的执行计划的执行情况信息,通常使用这个即可
  • “allPlansExecution” 表示展示执行计划信息,并展示被选中的执行计划的执行情况信息,还展示备选的执行计划的执行情况信息

展示结果如下:

{
	"queryPlanner" : {
		"plannerVersion" : 1,
		"namespace" : "maohw.orders",
		"indexFilterSet" : false,
		"parsedQuery" : {
			"$and" : [
				{
					"useCode" : {
						"$eq" : "james"
					}
				},
				{
					"orderTime" : {
						"$lt" : ISODate("2015-04-03T16:00:00Z")
					}
				}
			]
		},
		"winningPlan" : {
			"stage" : "COLLSCAN",
			"filter" : {
				"$and" : [
					{
						"useCode" : {
							"$eq" : "james"
						}
					},
					{
						"orderTime" : {
							"$lt" : ISODate("2015-04-03T16:00:00Z")
						}
					}
				]
			},
			"direction" : "forward"
		},
		"rejectedPlans" : [ ]
	},
	"executionStats" : {
		"executionSuccess" : true,
		"nReturned" : 4064,
		"executionTimeMillis" : 161,
		"totalKeysExamined" : 0,
		"totalDocsExamined" : 500001,
		"executionStages" : {
			"stage" : "COLLSCAN",
			"filter" : {
				"$and" : [
					{
						"useCode" : {
							"$eq" : "james"
						}
					},
					{
						"orderTime" : {
							"$lt" : ISODate("2015-04-03T16:00:00Z")
						}
					}
				]
			},
			"nReturned" : 4064,
			"executionTimeMillisEstimate" : 146,
			"works" : 500003,
			"advanced" : 4064,
			"needTime" : 495938,
			"needYield" : 0,
			"saveState" : 3910,
			"restoreState" : 3910,
			"isEOF" : 1,
			"invalidates" : 0,
			"direction" : "forward",
			"docsExamined" : 500001
		}
	},
	"serverInfo" : {
		"host" : "localhost.localdomain",
		"port" : 27017,
		"version" : "3.4.20",
		"gitVersion" : "447847d93d6e0a21b018d5df45528e815c7c13d8"
	},
	"ok" : 1
}

第三步:分析explain结果

explain相关项说明:

queryPlanner(执行计划描述)
	winningPlan(被选中的执行计划)
		stage(可选项:COLLSCAN 没有走索引,IXSCAN使用了索引)
	rejectedPlans(候选的执行计划)
executionStats(执行情况描述)
	nReturned (返回的文档个数)
	executionTimeMillis(执行时间ms)
	totalKeysExamined (检查的索引键值个数)
	totalDocsExamined (检查的文档个数)

优化目标:

  1. 根据需求建立索引
  2. 每个查询都要使用索引以提高查询效率, winningPlan.stage 必须为IXSCAN
  3. 追求totalDocsExamined = nReturned

索引优化实战

  1. 首先构造50w的数据,数据结构:

    private String id;
    private String orderCode;
    private String useCode;
    private Date orderTime;
    private BigDecimal price;
    

    构造函数这里我不贴出来,在我的github上有,请参看:github

  2. 测试语句

    db.orders.find({"useCode":"james", "orderTime" : 
            {"$lt" : new Date("2015-04-03T16:00:00.000Z")}}).explain('executionStats')
    

    在没有建立索引的情况下,上面这句话查询是非常耗时的,比如600ms

  3. 新建第一个单键索引

    db.orders.createIndex({"useCode":-1});
    

    发现会走useCode索引,查询时间降到160ms

  4. 新建一个复合索引

    db.orders.createIndex({"useCode":-1,"orderTime":-1});
    

    发现走了复合索引,查询时间可以降到18ms

  5. 最后演示复合索引的使用和索引顺序是有关系的,创建如下复合索引

    db.users.createIndex({username:1,age:-1})
    

    则如下的语句是否会走该索引的情况说明:

    用了索引:db.users.find().sort({username:1,age:-1}).explain("executionStats") 
    用了索引:db.users.find().sort({username:-1,age:1}).explain("executionStats") 
    不用索引:db.users.find().sort({username:-1,age:-1}).explain("executionStats") 
    不用索引:db.users.find().sort({age:-1,username:1}).explain("executionStats") 
    

    说明复合索引中的顺序是极其重要的,只要查询条件和复合索引中的全正向或全反向都可以用到索引,否则不走索引!!!

索引建议

  1. 索引很有用,但是它也是有成本的——它占内存,让写入变慢

  2. mongoDB通常在一次查询里使用一个索引,所以多个字段的查询或者排序需要复合索引才能更加高效

  3. 复合索引的顺序非常重要!!!

  4. 在生成环境构建索引往往开销很大,时间也不可以接受,在数据量庞大之前尽量进行查询优化和构建索引

  5. 避免昂贵的查询,使用查询分析器记录那些开销很大的查询便于问题排查

  6. 通过减少扫描文档数量来优化查询,使用explai对开销大的查询进行分析并优化

  7. 索引是用来查询小范围数据的,不适合使用索引的情况

  8. 每次查询都需要返回大部分数据的文档,避免使用索引

  9. 写比读多的场景也不适合使用索引

标签:username,users,MongoDB,db,查询,索引,createIndex,及慢
来源: https://blog.csdn.net/supermao1013/article/details/89739958