MongoDB索引详解-03

MongoDB索引

索引是一种用来快速查询数据的数据结构。B+Tree 就是一种常用的数据库索引数据结构,
MongoDB采用B+Tree 做索引 ,索引创建在colletions上。MongoDB不使用索引的查
询,先扫描所有的文档,再匹配符合条件的文档。 使用索引的查询,通过索引找到文档,
使用索引能够极大的提升查询效率。

索引的分类

按照索引包含的字段数量,可以分为单键索引和组合索引(或复合索引)。
按照索引字段的类型,可以分为主键索引和非主键索引。
按照索引节点与物理记录的对应方式来分,可以分为聚簇索引和非聚簇索引,其
中聚簇索引是指索引节点上直接包含了数据记录,而后者则仅仅包含一个指向数据记
录的指针。
按照索引的特性不同,又可以分为唯一索引、稀疏索引、文本索引、地理空间索 引等
与大多数数据库一样,MongoDB支持各种丰富的索引类型,包括单键索引、复合索 引,唯一索引等一些常用的结构。由于采用了灵活可变的文档类型,因此它也同样支持对嵌 套字段、数组进行索引。通过建立合适的索引,我们可以极大地提升数据的检索速度。在一些特殊应用场景,MongoDB还支持地理空间索引、文本检索索引、TTL索引等不同的特 性。

索引设计原则

1、每个查询原则上都需要创建对应索引
2、单个索引设计应考虑满足尽量多的查询
3、索引字段选择及顺序需要考虑查询覆盖率及选择性
4、对于更新及其频繁的字段上创建索引需慎重
5、对于数组索引需要慎重考虑未来元素个数
6、对于超长字符串类型字段上慎用索引
7、并发更新较高的单个集合上不宜创建过多索引

索引操作

创建索引

创建索引语法格式
db.collection.createIndex(keys, options)
Key 值为你要创建的索引字段,1 按升序创建索引, -1 按降序创建索引
可选参数列表如下:

Parameter

Type

Description

2

Boolean

引过程会阻塞其它数据库操作 ,background可指定 以后台方式创建索引 ,即增加 "background" 可选参     数。   "background" 默认值为false。

unique

Boolean

立的索引是否唯一。指定为true创建唯一索引。默认值 false.

name

string

索引名称。如果未指定,  MongoDB的通过连接索引的 字段名和排序顺序生成一个索引名称。

dropDups

Boolean

3.0 +版本已废。在建立唯一索引时是否删除重复记录, 指定 true 创建唯一索引。默认值为 false.

sparse

Boolean

文档中不存在的字段数据不启用索引;这个参数需要特 意 ,如果设置为true的话 ,在索引字段中不会查询出 不包含对应字段的文档。默认值为 false.

expireAfterSeconds

integer

指定个以秒为单位的数值 ,完成 TTL设定 ,设定集合的 生存时间。

v

index version

引的版本号。默认的索引版本取决于mongod创建索引 时运行的版本。

weights

document

索引权重值 ,数在 1 到 99,999 之间 ,表示该索引相对 于其他索引字段的得分权重。

default_language

string

对于文本索 ,该参数决定了停用词及词干和词器的规则 列表。   默认为英语

language_override

string

对于文索引 ,该参数指定了包含在文档中的字段名 ,语

# 创建索引后台执行
 db.values.createIndex({open: 1, close: 1}, {background: true})
 # 创建唯一索引
 db.values.createIndex({title:1},{unique:true})

查看索引

#查看索引信息
 db.books.getIndexes()
#查看索引键
 db.books.getIndexKeys()

查看索引占用空间

db.collection.totalIndexSize([is_detail])
is_detail:可选参数,传入除0或false外的任意数据,都会显示该集合中每 个索引的大小及总大小。如果传入0或false则只显示该集合中所有索引的总大小。默 认值为false。

删除索引

 

#删除集合指定索引
 db.col.dropIndex("索引名称")
 #删除集合所有索引 不能删除主键索引
 db.col.dropIndexes()

索引类型

单键索引

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

db.books.createIndex({title:1})

 

对内嵌文档字段创建索引:

复合索引 

复合索引是多个字段组合而成的索引,其性质和单字段索引类似。但不同的是, 复合索
引中字段的顺序、字段的升降序对查询性能有直接的影响 ,因此在设计复合索引时则需要考
虑不同的查询场景。

db.books.createIndex({type:1,favCount:1})

 

多键索引

 

在数组的属性上建立索引。 针对这个数组的任意值的查询都会定位到这个文档,既多个索引
入口或者键值引用同一个文档
创建  :  
db.inventory.createIndex( { ratings: 1 } )

注意 :  

多键索引很容易与复合索引产生混淆, 复合索引是多个字段的组合,而多键索引则仅仅是在
一个字段上出现了多键(multi key) 。而实质上,多键索引也可以出现在复合字段上
MongoDB并不支持一个复合索引中同时出现多个数组字段

地理空间索引(Geospatial Index)

在移动互联网时代,基于地理位置的检索(LBS)功能几乎是所有应用系统的标配。 MongoDB为地理空间检索提供了非常方便的功能。 地理空间索引(2dsphereindex)就是 专门用于实现位置检索的一种特殊索引。 案例:MongoDB如何实现“查询附近商家"? 假设商家的数据模型如下:

 

db.restaurant.insert({
    restaurantId: 0,
    restaurantName: "兰州牛肉面",
    location: {
        type: "Point",
        coordinates: [73.97, 40.77]
    }
})

创建一个2dsphere索引

db.restaurant.createIndex({location : "2dsphere"})

全文索引(Text Indexes)

MongoDB支持全文检索功能,可通过建立文本索引来实现简易的分词检索

db.reviews.createIndex( { comments: "text" } )
$text操作符可以在有text index的集合上执行文本检索。 $text将会使用空格和标点符 号作为分隔符对检索字符串进行分词, 并且对检索字符串中所有的分词结果进行一个逻辑 上的 OR 操作。
全文索引能解决快速文本查找的需求,比如有一个博客文章集合,需要根据博客的内容 来快速查找,则可以针对博客内容建立文本索引。
db.stores.insert([{
    _id: 1,
    name: "Java Hut",
    description: "Coffee and cakes"
},
{
    _id: 2,
    name: "Burger Buns",
    description: "Gourmet hamburgers"
},
{
    _id: 3,
    name: "Coffee Shop",
    description: "Just coffee"
},
{
    _id: 4,
    name: "Clothes Clothes Clothes",
    description: "Discount clothing"
},
{
    _id: 5,
    name: "Java Shopping",
    description: "Indonesian goods"
}])

创建 name和description的全文索引
db.stores.createIndex({name: "text", description: "text"})

 

 

通过$text操作符来查寻数据中所有包含“coffee”,”shop”,“java”列表中任何词语的 商店
db.stores.find({$text: {$search: "java coffee shop"}})

MongoDB的文本索引功能存在诸多限制,而官方并未提供中文分词的功能 ,这使得该功能
的应用场景十分受限。

Hash索引(Hashed Indexes)

不同于传统的B-Tree索引,哈希索引使用hash函数来创建索引。 在索引字段上进行精确匹配,
但不支持范围查询,不支持多键hash ; Hash索引上的入口是均匀分布的,在分片集合中非常
有用

 

 db.users.createIndex({username : 'hashed'})

通配符索引(Wildcard Indexes)

MongoDB的文档模式是动态变化的,而通配符索引可以建立在一些不可预知的字段上,以
此实现查询的加速
db.products.insert([{
    "product_name": "Spy Coat",
    "product_attributes": {
        "material": ["Tweed", "Wool", "Leather"],
        "size": {
            "length": 72,
            "units": "inches"
        }
    }
},
{
    "product_name": "Spy Pen",
    "product_attributes": {
        "colors": ["Blue", "Black"],
        "secret_feature": {
            "name": "laser",
            "power": "1000",
            "units": "watts",
        }
    }
},
{
    "product_name": "Spy Book"
}])

db.products.createIndex( { "product_attributes.$**" : 1 } )

通配符索引可以支持任意单字段查询 product_attributes或其嵌入字段

db.products.find( { "product_attributes.size.length" : { $gt : 60 } } )
db.products.find( { "product_attributes.material" : "Leather" } )
db.products.find( { "product_attributes.secret_feature.name" : "laser" })

 

 注意事项  : 通配符索引不兼容的索引类型或属性

通配符索引是稀疏的,不索引空字段。因此, 通配符索引不能支持查询字段不存 在的文档

 

#通配符索引不能支持以下查询
db.products.find({
    "product_attributes": {
        $exists: false
    }
}) 
db.products.aggregate([{
    $match: {
        "product_attributes": {
            $exists: false
        }
    }
}])
通配符索引为文档或数组的内容生成条目,而不是文档/数组本身。因此 通配符 索引不能支持精确的文档/数组相等匹配。 通配符索引可以支持查询字段等于空文档{} 的情况。
#通配符索引不能支持以下查询:
 db.products.find({ "product_attributes.colors" : [ "Blue", "Black" ] } )

 db.products.aggregate([{
 $match : { "product_attributes.colors" : [ "Blue", "Black" ] }
 }])

索引属性

唯一索引(Unique Indexes)

在现实场景中,唯一性是很常见的一种索引约束需求,重复的数据记录会带来许多处理上的
麻烦,比如订单的编号、用户的登录名等。通过建立唯一性索引,可以保证集合中文档的指
定字段拥有唯一值。
唯一性索引对于文档中缺失的字段,会使用null值代替 ,因此不允许存在多个文
档缺失索引字段的情况。
对于分片的集合,唯一性约束必须匹配分片规则。 换句话说,为了保证全局的唯
一性,分片键必须作为唯一性索引的前缀字段。

部分索引(Partial Indexes)

部分索引仅对满足指定过滤器表达式的文档进行索引。 通过在一个集合中为文档的一个
子集建立索引, 部分索引具有更低的存储需求和更低的索引创建和维护的性能成本。 3.2
版功能
部分索引提供了稀疏索引功能的超集,应该优先于稀疏索引
db.restaurants.createIndex(
 { cuisine: 1, name: 1 },
 { partialFilterExpression: { rating: { $gt: 5 } } }
 )
partialFilterExpression选项接受指定过滤条件的文档:
等式表达式(例如:field: value或使用$eq操作符)
$exists: true
$gt, $gte, $lt, $lte
$type
顶层的$and
​​​​​​​# 符合条件,使用索引
 db.restaurants.find( { cuisine: "Italian", rating: { $gte: 8 } } )
 # 不符合条件,不能使用索引
 db.restaurants.find( { cuisine: "Italian" } )
唯一约束结合部分索引使用导致唯一约束失效的问题
注意:如果同时指定了 partialFilterExpression 和唯一约束,那么唯一约束只适用于满足筛选
器表达式的文档。 如果文档不满足筛选条件,那么带有惟一约束的部分索引不会阻止插入不
满足惟一约束的文档。

稀疏索引(Sparse Indexes)

索引的稀疏属性确保 索引只包含具有索引字段的文档的条目,索引将跳过没有索引字段
的文档。
特性: 只对存在字段的文档进行索引(包括字段值为 null 的文档)
#不索引不包含xmpp_id字段的文档
db.addresses.createIndex( { "xmpp_id": 1 }, { sparse: true } )
如果稀疏索引会导致查询和排序操作的结果集不完整,MongoDB将不会使用该索引 , 除非hint()明确指定索引。
同时具有稀疏性和唯一性的索引 可以防止集合中存在字段值重复的文档,但允许不包含 此索引字段的文档插入。

TTL索引(TTL Indexes)

在一般的应用系统中,并非所有的数据都需要永久存储。例如一些系统事件、用户消息等,
这些数据随着时间的推移,其重要程度逐渐降低。更重要的是,存储这些大量的历史数据需
要花费较高的成本,因此 项目中通常会对过期且不再使用的数据进行老化处理。
通常的做法如下:
方案一: 为每个数据记录一个时间戳,应用侧开启一个定时器,按时间戳定期删除过期的数
据。
方案二: 数据按日期进行分表,同一天的数据归档到同一张表,同样使用定时器删除过期的
表。
对于数据老化,MongoDB提供了一种更加便捷的做法:TTL(
Time To Live)索引。 TTL
索引需要声明在一个日期类型的字段中,TTL 索引是特殊的单字段索引 ,MongoDB 可以
使用它在一定时间或特定时钟时间后自动从集合中删除文档。
db.log_events.insertOne( {
 "createdAt": new Date(),
 "logEvent": 2,
 "logMessage": "Success!"
 } )
db.log_events.createIndex( { "createdAt": 1 }, { expireAfterSeconds: 20 })

 最后被清理掉了,并没有查询到数据

使用约束
TTL索引的确可以减少开发的工作量,而且通过数据库自动清理的方式会更加高效、可靠,
但是在使用TTL索引时需要注意以下的限制:
TTL索引只能支持单个字段,并且必须是非_id字段。 TTL索引不能用于固定集合。
TTL索引无法保证及时的数据老化 ,MongoDB会通过后台的TTLMonitor定时器
来清理老化数据,默认的间隔时间是1分钟。当然如果在数据库负载过高的情况下,
TTL的行为则会进一步受到影响。
TTL索引对于数据的清理仅仅使用了remove命令,这种方式并不是很高效。
此TTL Monitor在运行期间对系统CPU、磁盘都会造成一定的压力。 相比之下,按日
期分表的方式操作会更加高效。
日志存储:
日期分表
固定集合
TTL索引
插入: writeConcern:{w:0}

索引使用建议

1.为每一个查询建立合适的索引

这个是针对于数据量较大比如说超过几十上百万(文档数目)数量级的集合。如果没有
索引MongoDB需要把所有的Document从盘上读到内存,这会对MongoDB服务器造成较
大的压力并影响到其他请求的执行。

2.创建合适的复合索引,不要依赖于交叉索引

如果你的查询会使用到多个字段,MongoDB有两个索引技术可以使用:交叉索引和复
合索引。 交叉索引就是针对每个字段单独建立一个单字段索引 然后在查询执行时候使用相 应的单字段索引进行索引交叉而得到查询结果。 交叉索引目前触发率较低,所以如果你有一
个多字段查询的时候,建议使用复合索引能够保证索引正常的使用。

3.复合索引字段顺序:匹配条件在前,范围条件在后(Equality First, Range After)

在创建复合索引时如果条件有匹配和范围之分,那么匹配条件(sport: “marathon”) 应该在复合索引的前面。
范围条件(age: <30)字段应该放在复合索引的后 面。

4.尽可能使用覆盖索引(Covered Index)

建议只返回需要的字段,同时,利用覆盖索引来提升性能。

5.建索引要在后台运行

在对一个集合创建索引时,该集合所在的数据库将不接受其他读写操作。 对大数据量的集合 建索引,建议使用后台运行选项 {background: true}

6.避免设计过长的数组索引

数组索引是多值的,在存储时需要使用更多的空间。如果索引的数组长度特别长,或者数组
的增长不受控制,则可能导致索引空间急剧膨胀。

explain执行计划详解

通常我们需要关心的问题:
查询是否使用了索引
索引是否减少了扫描的记录数量
是否存在低效的内存排序
MongoDB 提供了 explain 命令,它可以帮助我们评估指定查询模型(querymodel)的执行计 划, 根据实际情况进行调整,然后提高查询效率。
explain() 方法的形式如下 :
db.collection.find().explain(<verbose>)

模式名字

queryPlanner

执行计划的详细信息 ,包括查询计划、集合信息、查询条件、最佳执行划、查询方式和 MongoDB 服务信息等

exectionStats

佳执行计划的执行情况和被拒绝的计划等信息

allPlansExecution

选择并执行最佳执行计划 ,并返回最佳执行计划和其他执行计划的执行情 况

queryPlanner
# 未创建title的索引
db.books.find({title:"book‐1"}).explain("queryPlanner")

字段名称

plannerVersion

执行计划的版本

namespace

查询的集合

indexFilterSet

否使用索引

parsedQuery

查询条

winningPlan

最佳执行计划

stage

查询方

filter

过滤条件

direction

查询顺

rejectedPlans

拒绝的执行计划

serverInfo

mongodb服务器信息

 executionStats

executionStats 模式的返回信息中包含了 queryPlanner 模式的所有字段,并且还包含了
最佳执行计划的执行情况
#创建索引
db.books.createIndex({title:1})
 db.books.find({title:"book‐1"}).explain("executionStats")
{
	"createdCollectionAutomatically" : true,
	"numIndexesBefore" : 1,
	"numIndexesAfter" : 2,
	"ok" : 1
}
> db.books.find({title:"book‐1"}).explain("executionStats")
{
	"queryPlanner" : {
		"plannerVersion" : 1,
		"namespace" : "restaurant.books",
		"indexFilterSet" : false,
		"parsedQuery" : {
			"title" : {
				"$eq" : "book‐1"
			}
		},
		"winningPlan" : {
			"stage" : "FETCH",
			"inputStage" : {
				"stage" : "IXSCAN",
				"keyPattern" : {
					"title" : 1
				},
				"indexName" : "title_1",
				"isMultiKey" : false,
				"multiKeyPaths" : {
					"title" : [ ]
				},
				"isUnique" : false,
				"isSparse" : false,
				"isPartial" : false,
				"indexVersion" : 2,
				"direction" : "forward",
				"indexBounds" : {
					"title" : [
						"[\"book‐1\", \"book‐1\"]"
					]
				}
			}
		},
		"rejectedPlans" : [ ]
	},
	"executionStats" : {
		"executionSuccess" : true,
		"nReturned" : 0,
		"executionTimeMillis" : 0,
		"totalKeysExamined" : 0,
		"totalDocsExamined" : 0,
		"executionStages" : {
			"stage" : "FETCH",
			"nReturned" : 0,
			"executionTimeMillisEstimate" : 0,
			"works" : 1,
			"advanced" : 0,
			"needTime" : 0,
			"needYield" : 0,
			"saveState" : 0,
			"restoreState" : 0,
			"isEOF" : 1,
			"docsExamined" : 0,
			"alreadyHasObj" : 0,
			"inputStage" : {
				"stage" : "IXSCAN",
				"nReturned" : 0,
				"executionTimeMillisEstimate" : 0,
				"works" : 1,
				"advanced" : 0,
				"needTime" : 0,
				"needYield" : 0,
				"saveState" : 0,
				"restoreState" : 0,
				"isEOF" : 1,
				"keyPattern" : {
					"title" : 1
				},
				"indexName" : "title_1",
				"isMultiKey" : false,
				"multiKeyPaths" : {
					"title" : [ ]
				},
				"isUnique" : false,
				"isSparse" : false,
				"isPartial" : false,
				"indexVersion" : 2,
				"direction" : "forward",
				"indexBounds" : {
					"title" : [
						"[\"book‐1\", \"book‐1\"]"
					]
				},
				"keysExamined" : 0,
				"seeks" : 1,
				"dupsTested" : 0,
				"dupsDropped" : 0
			}
		}
	},
	"serverInfo" : {
		"host" : "192.168.30.130",
		"port" : 27017,
		"version" : "4.4.9",
		"gitVersion" : "b4048e19814bfebac717cf5a880076aa69aba481"
	},
	"ok" : 1
}

字段名称

winningPlan.inputStage

用来描述子stage ,并且为其父stage提供文档和 索引关键字

winningPlan.inputStage.stage

子查询方

winningPlan.inputStage.keyPattern

所扫描的index内容

winningPlan.inputStage.indexName

索引名

winningPlan.inputStage.isMultiKey

是否是Multikey。如果索引建立在array上 ,将是 true

executionStats.executionSuccess

否执行成功

executionStats.nReturned

返回的个数

executionStats.executionTimeMillis

这条语句执行时间

executionStats.executionStages.executionTim eMillisEstimate

检索文档获取数据的时间

executionStats.executionStages.inputStage.ex ecutionTimeMillisEstimate

扫描获取数据的时间

executionStats.totalKeysExamined

索引扫描次数

executionStats.totalDocsExamined

文档扫描次数

executionStats.executionStages.isEOF

是否到达 steam 结尾,  1 或者 true 代表已到达结 尾

executionStats.executionStages.works

作单元数 ,一个查询会分解成小的工作单元

executionStats.executionStages.advanced

优先返回的结果数

executionStats.executionStages.docsExamine

文档检查

allPlansExecution返回的信息包含 executionStats 模式的内容,且包含
allPlansExecution:[]块
stage状态

COLLSCAN

全表扫

IXSCAN

索引扫

FETCH

根据索引检索指定文档

SHARD_MERGE

将各个分片返回数据进行合并

SORT

在内存中进行了排序

LIMIT

使用limit制返回数

SKIP

使用skip行跳过

IDHACK

对_id行查询

SHARDING_FILTER

过mongos对分片数据进行查询

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)
COUNTSCAN(不使用index进行count)
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值