mongodb-索引

402 阅读8分钟

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

MongoDB索引数据结构是B+Tree;

索引的分类

  • 按照索引包含的字段数量,可以分为单键索引和组合索引(或复合索引)。
  • 按照索引字段的类型,可以分为主键索引和非主键索引。
  • 按照索引节点与物理记录的对应方式来分,可以分为聚簇索引和非聚簇索引,其中聚簇索引是指索 引节点上直接包含了数据记录,而后者则仅仅包含一个指向数据记录的指针。
  • 按照索引的特性不同,又可以分为唯一索引、稀疏索引、文本索引、地理空间索引等

与大多数数据库一样,MongoDB支持各种丰富的索引类型,包括单键索引、复合索引,唯一索引等一 些常用的结构。由于采用了灵活可变的文档类型,因此它也同样支持对嵌套字段、数组进行索引。通过 建立合适的索引,我们可以极大地提升数据的检索速度。在一些特殊应用场景,MongoDB还支持地理 空间索引、文本检索索引、TTL索引等不同的特性。

创建索引语法格式

db.collection.createIndex(keys, options)

Key 值为你要创建的索引字段,1 按升序创建索引, -1 按降序创建索引

创建唯一索引 db.values.createIndex({title:1},{unique:true})

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

删除集合指定索引 db.col.dropIndex("索引名称")

单键索引

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

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

对内嵌文档字段创建索引:db.books.createIndex({"author.name":1})

复合索引

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

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

多键索引

在数组的属性上建立索引。针对这个数组的任意值的查询都会定位到这个文档,既多个索引入口或者键值 引用同一个文档。针对数组

db.inventory.createIndex( { ratings: 1 } )

在包含嵌套对象的数组字段上创建多键索引db.inventory.createIndex( { "stock.size": 1, "stock.quantity": 1 } )

地理空间索引

在移动互联网时代,基于地理位置的检索(LBS)功能几乎是所有应用系统的标配。MongoDB为地理空 间检索提供了非常方便的功能。地理空间索引(2dsphereindex)就是专门用于实现位置检索的一种特 殊索引。

创建一个2dsphere索引db.restaurant.createIndex({location : "2dsphere"})

例如:查询附近10000米商家信息

db.restaurant.find( { location:{ $near :{ $geometry :{ type : "Point" , coordinates : [ -73.88, 40.78 ] } , $maxDistance:10000 } } } )

全文索引

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

db.reviews.createIndex( { comments: "text" } )

$text操作符可以在有text index的集合上执行文本检索。$text将会使用空格和标点符号作为分隔符对检 索字符串进行分词, 并且对检索字符串中所有的分词结果进行一个逻辑上的 OR 操作。

全文索引能解决快速文本查找的需求,比如有一个博客文章集合,需要根据博客的内容来快速查找,则 可以针对博客内容建立文本索引。

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

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

对中文支持有限,还是使用es

Hash索引

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

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

通配符索引

MongoDB的文档模式是动态变化的,而通配符索引可以建立在一些不可预知的字段上,以此实现查询 的加速。

创建通配符索引: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" } )

索引属性

唯一索引(Unique Indexes) 在现实场景中,唯一性是很常见的一种索引约束需求,重复的数据记录会带来许多处理上的麻烦,比如 订单的编号、用户的登录名等。通过建立唯一性索引,可以保证集合中文档的指定字段拥有唯一值。

创建唯一索引 db.values.createIndex({title:1},{unique:true})

复合索引支持唯一性约束 db.values.createIndex({title:1,type:1},{unique:true})

部分索引:部分索引仅对满足指定过滤器表达式的文档进行索引。通过在一个集合中为文档的一个子集建立索引, 部分索引具有更低的存储需求和更低的索引创建和维护的性能成本。3.2新版功能。 部分索引提供了稀疏索引功能的超集,应该优先于稀疏索引。

符合条件,使用索引 db.restaurants.find( { cuisine: "Italian", rating: { $gte: 8 } } )

不符合条件,不能使用索引 db.restaurants.find( { cuisine: "Italian" } )

稀疏索引

索引的稀疏属性确保索引只包含具有索引字段的文档的条目,索引将跳过没有索引字段的文档。 特性: 只对存在字段的文档进行索引(包括字段值为null的文档)

不索引不包含xmpp_id字段的文档 db.addresses.createIndex( { "xmpp_id": 1 }, { sparse: true } )

如果稀疏索引会导致查询和排序操作的结果集不完整,MongoDB将不会使用该索引,除非hint()明确指定索引。

使用稀疏索引 db.scores.find( { score: { $lt: 90 } } ) 不会走索引,有些数据索引字段不存在。

即使排序是通过索引字段,MongoDB也不会选择稀疏索引来完成查询,以返回完整的结果 db.scores.find().sort( { score: -1 } )

要使用稀疏索引,使用hint()显式指定索引 db.scores.find().sort( { score: -1 } ).hint( { score: 1 } ) 数据丢失

TTL索引

对于数据老化,MongoDB提供了一种更加便捷的做法:TTL(Time To Live)索引。TTL索引需要声明 在一个日期类型的字段中,TTL 索引是特殊的单字段索引,MongoDB 可以使用它在一定时间或特定时 钟时间后自动从集合中删除文档。

创建 TTL 索引,TTL 值为3600秒 db.log_events.createIndex( { "createdAt": 1 }, { expireAfterSeconds: 20 } )

TTL索引时需要注意以下的限制:

  • TTL索引只能支持单个字段,并且必须是非_id字段。

  • TTL索引不能用于固定集合。

  • TTL索引无法保证及时的数据老化,MongoDB会通过后台的TTLMonitor定时器来清理老化数据, 默认的间隔时间是1分钟。当然如果在数据库负载过高的情况下,TTL的行为则会进一步受到影响。

  • TTL索引对于数据的清理仅仅使用了remove命令,这种方式并不是很高效。因此TTL Monitor在运 行期间对系统CPU、磁盘都会造成一定的压力。相比之下,按日期分表的方式操作会更加高效。

隐藏索引

隐藏索引对查询规划器不可见,不能用于支持查询。通过对规划器隐藏索引,用户可以在不实际删除索 引的情况下评估删除索引的潜在影响。如果影响是负面的,用户可以取消隐藏索引,而不必重新创建已 删除的索引。为了避免删除索引带来意想不到的问题,可以先隐藏。

创建隐藏索引 db.restaurants.createIndex({ borough: 1 },{ hidden: true });

隐藏现有索引 db.restaurants.hideIndex( { borough: 1} ); db.restaurants.hideIndex( "索引名称" )

取消隐藏索引 db.restaurants.unhideIndex( { borough: 1} );

索引使用建议:

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

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

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

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

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

explain执行计划详解

  • 查询是否使用了索引

  • 索引是否减少了扫描的记录数量

  • 是否存在低效的内存排序

explain()方法的形式如下:

db.collection.find().explain()

verbose 可选参数,表示执行计划的输出模式,默认queryPlanner

执行计划的返回结果中尽量不要出现以下stage:

  • COLLSCAN(全表扫描)

  • SORT(使用sort但是无index)

  • 不合理的SKIP

  • SUBPLA(未用到index的$or)

  • COUNTSCAN(不使用index进行count)