MongoDB学习笔记之索引(二)

 索引属性

唯一索引(Unique Indexes)

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

#创建唯一索引
db.values.createIndex({titile:1},{unique:true})
#复合索引支持唯一性约束
db.values.createIndex({title:1,type:1},{unique:true})
#多键索引支持唯一性约束
db.inventory.createIndex({ratings:1},{unique:true})
  • 唯一性索引对于文档中缺失的文档,会使用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"})

案例1:

restaurants集合数据

db.restaurants.insert({
   "_id" : ObjectId("5641f6a7522545bc535b5dc9"),
   "address" : {
      "building" : "1007",
      "coord" : [
         -73.856077,
         40.848447
      ],
      "street" : "Morris Park Ave",
      "zipcode" : "10462"
   },
   "borough" : "Bronx",
   "cuisine" : "Bakery",
   "rating" : { "date" : ISODate("2014-03-03T00:00:00Z"),
                "grade" : "A",
                "score" : 2
              },
   "name" : "Morris Park Bake Shop",
   "restaurant_id" : "30075445"
})

创建索引

db.restaurants.createIndex(
 { borough: 1, cuisine: 1 },
 { partialFilterExpression: { 'rating.grade': { $eq: "A" } } }
)

测试:

db.restaurants.find( { borough: "Bronx", 'rating.grade': "A" } )
db.restaurants.find( { borough: "Bronx", cuisine: "Bakery" } )

唯一约束结合部分索引使用导致唯一约束失效的问题

注意:如果同时指定了partialFilterExpression和唯一约束,那么唯一约束只适用于满足筛选器表达式的文档。

如果文档不满足筛选条件,那么带有惟一约束的部分索引不会阻止插入不满足惟一约束的文档。

案例:

users集合数据准备

db.users.insertMany( [
   { username: "david", age: 29 },
   { username: "amanda", age: 35 },
   { username: "rajiv", age: 57 }
] )

创建索引,指定username字段和部分过滤器表达式age: {$gte: 21}的唯一约束。

db.users.createIndex(
   { username: 1 },
   { unique: true, partialFilterExpression: { age: { $gte: 21 } } }
)

索引防止了以下文档的插入,因为文档已经存在,且指定的用户名和年龄字段大于21:

db.users.insertMany( [
   { username: "david", age: 27 },
   { username: "amanda", age: 25 },
   { username: "rajiv", age: 32 }
] )

但是,以下具有重复用户名的文档是允许的,因为唯一约束只适用于年龄大于或等于21岁的文档。

db.users.insertMany( [
   { username: "david", age: 20 },
   { username: "amanda" },
   { username: "rajiv", age: null }
] )

稀疏索引(Sparse Indexse)

索引的稀疏属性确保索引只包含有索引字段的文档条目,索引将跳过没有索引字段的文档。

特性:只对存在的字段的文档进行索引(包括字段值为null的文档)

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

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

案例:数据准备

db.scores.insertMany([
    {"userid" : "newbie"},
    {"userid" : "abby", "score" : 82},
    {"userid" : "nina", "score" : 90}
])

创建稀疏索引

db.scores.createIndex({score:1},{sparse:true})

索引测试:

# 使用稀疏索引
db.scores.find( { score: { $lt: 90 } } )

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

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

同时具有稀疏性和唯一性的索引可以防止集合中存在字段值重复的文档,但允许不包含此索引字段的文档插入。

案例:

# 创建具有唯一约束的稀疏索引
db.scores.createIndex( { score: 1 } , { sparse: true, unique: true } )

测试

这个索引将允许插入具有唯一的分数字段值或不包含分数字段的文档。因此,给定scores集合中的现有文档,索引允许以下插入操作:

db.scores.insertMany( [
   { "userid": "AAAAAAA", "score": 43 },
   { "userid": "BBBBBBB", "score": 34 },
   { "userid": "CCCCCCC" },
   { "userid": "CCCCCCC" }
] )

索引不允许添加下列文件,因为已经存在评分为82和90的文件:

db.scores.insertMany( [
   { "userid": "AAAAAAA", "score": 82 },
   { "userid": "BBBBBBB", "score": 90 }
] )

TTL索引(TTL Indexes)

在一般的应用系统中,并非所有的数据都需要永久存储。例如一些系统时间、用户消息等,这些数据随着时间的推移,其重要程度逐渐降低。更重要的是,存储这些大量的历史数据需要花费较高的成本,因此项目中通常会对过期且不再使用的数据进行老化处理。

方案一:为每个数据记录一个时间戳,应用侧开启一个定时器,按时间戳定期删除过期的数据。

方案二:数据按日期进行分表,同一天的数据归档到同一张表,同样使用定时器删除过期的表。

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

#创建TTL索引,TTL值为3600秒
db.eventlog.createIndex({"lastModifiedDate":1},{expireAfterSeconds:3600})

对集合创建TTL索引之后,MongoDB会在周期性运行的后台线程中对该集合进行检查及数据清理工作。除了数据老化功能,TTL索引具有普通索引的功能,同样可以用于加速数据的查询。

TTL索引不保证过期数据会在过期后立即被删除。文档过期和MongoDB从数据库中删除文档的时间之间可能存在延迟。删除过期文档的后台任务每60秒运行一次。因此,在文档到期和后台任务运行之间的时间段内,文档可能会保留在集合中。

案例:数据准备

db.log_events.insertOne({
    "createdAt": new Date(),
    "logEvent": 2,
    "logMessage": "Success!"
})

创建TTL索引

db.log_events.createIndex({"createdAt":1},{expireAfterSeconds:20})

可变的过期时间

TTL索引在创建之后,仍然可以对过期时间进行修改,这需要使用collMod命令对索引的定义进行变更。

db.runCommand({collMod:"log_events",index:{keyPattern:{createdAt:1},expireAfterSeconds:600}})

使用约束

TTL索引的确可以减少开发的工作量,而且通过数据库自动清理的方式会更加高效、可靠,但是在使用TTL索引时需要注意以下的限制

  • TTL索引只能支持单个字段,并且必须是非_id字段
  • TTL索引不能用于固定集合
  • TTL索引无法保证及时的数据老化,MongoDB会通过后台的TTLMonitor定时器来清理老化数据,默认的间隔时间是1分钟。当然如果在数据库负载过高的情况下,TTL的行为则会进一步影响。
  • TTL索引对于数据的清理仅仅使用了remove命令,这种方式并不是很高效。因此TTLMonitor在运行期间对系统CPU、磁盘都会造成一定的压力。相比之下,按日期分表的方式操作会更加高效。

隐藏索引(Hidden Indexes)

隐藏索引对查询规划器不可见,不能用于支持查询。通过对规划器隐藏索引,用户可以在不实际删除索引的情况下评估删除索引的潜在影响。如果影响是负面的,用户可以取消隐藏索引,而不必重新创建已删除的索引。4.4新版功能

#创建隐藏索引
db.restaurants.createIndex({ borough: 1 },{ hidden: true});
#隐藏现有索引
db.restaurants.hideIndex({ borough: 1 });
db.restaurants.hideIndex("索引名称");
#取消隐藏索引
db.restaurants.unhideIndex({ borough: 1 });
db.restaurants.unhideIndex("索引名称");

案例:

db.scores.insertMany([
    {"userid" : "newbie"},
    {"userid" : "abby", "score" : 82},
    {"userid" : "nina", "score" : 90}
])

创建隐藏索引

db.scores.createIndex(
    { userid: 1 },
    { hidden: true }
)

查看索引信息

db.scores.getIndexes()

索引属性hidden只在值为true时返回

 测试

#不使用索引
db.scores.find({userid:"abby"}).explain();
#取消隐藏索引
db.scores.unhideIndex({userid:1})
#使用索引
db.scores.find({userid:"abby"}).explain()

索引使用建议

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

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

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

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

#查询所有年龄小于30岁的深圳市马拉松运动员
db.athelets.find({sport:"marathon",location:"sz",age:{$lt:30}})
#创建复合索引
db.athelets.createIndex({sport:1,location:1,age:1})

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

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

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

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

5、建索引要在后台运行

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

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

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

explain执行计划详解

索引关心的问题

1、查询是否使用了索引    2、索引是否减少了扫描的记录数量   3、是否存在低效的内存排序

MongoDB提供了explain命令,他可以帮助我们评估指定查询模型(querymodel)的执行计划,根据实际情况进行调整,然后提高查询效率。

explain()方法的形式如下:

db.collection.find().explain(<verbose>)
  • verbose 可选参数,表示执行计划的输出模式,默认queryPlanner

模式名字

描述

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");

字段名称

描述

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.executionTimeMillisEstimate

检索文档获取数据的时间

executionStats.executionStages.inputStage.executionTimeMillisEstimate

扫描获取数据的时间

executionStats.totalKeysExamined

索引扫描次数

executionStats.totalDocsExamined

文档扫描次数

executionStats.executionStages.isEOF

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

executionStats.executionStages.works

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

executionStats.executionStages.advanced

优先返回的结果数

executionStats.executionStages.docsExamined

文档检查数

allPlansExecution

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

"allPlansExecution" : [ { "nReturned" : <int>, "executionTimeMillisEstimate" : <int>, "totalKeysExamined" : <int>, "totalDocsExamined" :<int>, "executionStages" : { "stage" : <STAGEA>, "nReturned" : <int>, "executionTimeMillisEstimate" : <int>, ... } } }, ... ]

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)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

30岁老阿姨

支持一下哦!!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值