【原文地址】https://docs.mongodb.com/manual/git
CRUD操做(四)正则表达式
1 查询方案(Query Plans)mongodb
MongoDB 查询优化程序处理查询而且针对给定可利用的索引选择最有效的查询方案。而后每次执行查询时,查询系统使用此查询方案。shell
查询优化程序仅缓存可能有多种切实可行的方案的查询计划。数据库
对于每个查询,查询规划者在查询方案高速缓存中搜索适合查询形式的查询方案。若是没有匹配的查询方案,查询规划者生成几个备选方案并在一个实验周期内作出评估。查询规划者选择获胜的方案,建立包含获胜方案的高速缓存条目,并使用它得到查询结果文档。数组
若是匹配条目存在,查询规划者基于匹配条目生成一个方案,经过从新规划方案机制评估此方案的性能。这个机制会根据此查询方案的性能作出经过或否决的决定并保持或者剔除此查询方案。若是此方案被剔除,那么查询计划会使用通常规划进程选择一个新方案并缓存它。查询规划者执行这个方案并返回查询结果。缓存
下面这个图说明了查询规划者的处理逻辑:服务器
你可使用db.collection.explain()或者cursor.explain()方法查看给定查询的查询方案。这些信息有助于定出索引策略。网络
db.collection.explain() 提供了其余操做的执行信息,例如db.collection.update()操做。app
查询方案高速缓存刷新
像索引或者删除集合这样的目录操做会刷新查询方案高速缓存。
若是mongod 重启或者关闭,查询高速缓存会被刷新。
2.6版本中,MongoDB 提供了查询缓存方法来查看和修改缓存的查询计划。PlanCache.clear()方法会刷新整个查询高速缓存。
使用PlanCache.clearPlansByQuery()可清空指定的查询计划缓存。
索引过滤器
2.6版本中新增。
索引过滤器决定了由优化程序评估出的索引中哪些供查询模型使用。一个查询模型由查询、排序、投影规范的组合构成。若是一个给定的查询模型中存在索引过滤器,优化程序只考虑索引过滤器中指定的那些索引。
当查询模型中存在索引过滤器时,MongoDB 忽略hint()方法。为了查看是否在查询模型中使用了索引过滤器,查看执行db.collection.explain() 或
cursor.explain()方法返回文档中的字段indexFilterSet 。
索引过滤器仅做用于优化程序评估出的那些索引;对于一个给定的索引模型,优化程序可能仍会扫描那一集合做为获胜的方案。
索引过滤器存在于服务器执行操做的过程当中而且关机后不会被保留。MongoDB 也提供了手动移除过滤器的命令。
由于索引过滤器优先于优化程序的预期行为和hint() 方法,因此谨慎地使用索引过滤器。
经过减小读操做处理的数据量,索引改进了读操做的效率。这简化了MongoDB中与查询有关的工做。
2.1 建立索引以支持读操做
若是你的应用查询集合中的特定字段或一系列字段,那么被查询字段上的索引或者一系列被查询字段上的联合索引(compound index)可以防止查询过程当中对整个集合进行扫描。
例子
一个应用查询集合inventory 中的字段type ,字段type的值是由用户驱动的。
var typeValue = <someUserInput>;
db.inventory.find( { type: typeValue } );
为了提升性能,集合inventory 中的字段type上建立升序或降序索引。在mongo shell中可以使用db.collection.createIndex()方法建立索引。
db.inventory.createIndex( { type: 1 } )
索引可以阻止扫描整个inventory集合。
为了分析查询性能,请看查询性能分析这一节。
另外,为了优化读操做,索引支持排序操做和考虑更有效的存储利用。
对于单字段索引,选择升序仍是降序排序是不重要的。而对于复合索引是重要的。
2.2查询选择性
查询选择性涉及到了查询谓词怎样排除或过滤掉集合中的文档。查询选择性可以决定查询是否有效的利用索引或根本不使用索引。
更具选择性的查询匹配到的文档比例更小。例如_id 字段的相等匹配条件具备很高的选择性,由于它最多能匹配到一个文档。
选择性越低的查询匹配到的文档比例越大。选择性低的查询不能有效地利用索引甚至不能利用索引。
例如,不相等操做符$nin 和$ne不是更具选择性的,由于它们一般匹配到了已索引的大部分数据。结果,在不少状况下,使用$nin 或$ne操做符查询已索引的数据没有必须扫描集合中全部文档效率高。
正则表达式的选择性取决于表达式自己。
2.3覆盖查询
覆盖查询是这样一种查询,使用一个索引就能够知足查询需求而且没必要检查任何文档。当同时知足下面两个条件时,一个索引就能知足查询须要:
例如,集合inventory 中的字段type 和item具备下面的索引:
db.inventory.createIndex( { type: 1, item: 1 } )
索引会覆盖下面的操做,查询type 和item,并只返回item。
db.inventory.find(
{ type: "food", item:/^c/ },
{ item: 1, _id: 0 }
)
对于指定索引用于覆盖查询,投影器文档必须明确指定_id: 0 以便从结果集中排除_id字段,由于上面建立的索引不包含_id字段。
性能
由于索引包含了查询所需所有字段,因此使用一个索引MongoDB就能即匹配查询条件又能够返回所需结果。
仅查询那个索引比查询那个索引以外的文档要快得多。索引键一般都比目录文档要小的多,索引键一般在内存中或连续地存储于磁盘上。
限制
索引字段上的限制
若是出现下面的状况,一个索引就不可以覆盖一个查询:
集合中有一个文档包含一个数组类型的字段。若是有一个字段是数组,那么这个索引就变成了多键值索引(multi-key index)而且其不支持覆盖查询。
查询谓词中的字段或者投影器返回字段是嵌入式文档字段。例如,下面的集合users :
{ _id: 1, user: { login: "tester" } }
集合有下面的索引:
{ "user.login": 1 }
由于{ "user.login": 1 }为嵌入式文档字段上的索引,因此不能覆盖查询。
db.users.find(
{ "user.login": "tester" }, { "user.login": 1, _id: 0 }
)
上面这个查询仍然可使用{ "user.login": 1 }索引来找到匹配的文档,可是它会检测并获取检索所需文档。
分片集合上的限制
当运行一个mongos ,索引不能覆盖分片集合上的查询,若是索引不包含片键,但对_id索引有以下例外:若是查询分片集合仅仅指定关于_id字段的查询条件而且仅返回_id字段,那么运行一个mongos ,即便_id字段不是片键,_id索引也能覆盖查询。
3.0版本的变化:以前的版本运行一个mongos,一个索引不能覆盖一个分片集合上的查询。
解释
为了肯定一个查询是不是覆盖查询,可以使用db.collection.explain() 或explain() 方法,并查看返回结果(results)。
2.4 评估当前操做的性能
使用数据库分析器评估当前操做的性能
MongoDB 提供了数据库分析器来展示每个操做的特性。使用数据库分析器加载当前运行缓慢的查询或者写操做。例如,你能够利用这些信息决定建立何种索引。
使用db.currentOp()评估mongod操做
db.currentOp() 方法给出一个关于运行在mongod实例上的操做的性能报告。
使用explain 评估查询性能
cursor.explain() 和db.collection.explain()方法返回查询执行的信息,例如MongoDB 选出的完成查询和执行统计的索引。你能够选择
queryPlanner 模式, executionStats 模式, 或allPlansExecution 模式来执行上述两个方法以控制返回的信息量。
例如:
在mongo shell中,使用cursor.explain() 和 查询条件{ a: 1 }在集合records中查找文档:
db.records.find( { a: 1 } ).explain("executionStats")
2.5 优化查询性能
建立索引支持查询
为普通查询建立索引。若是一个查询检索多个字段,那么建立复合索引(compound index)。扫描索引比扫描集合更快。索引结构比文档引用小,文档引用按必定的顺序存储。
例子
若是有一个集合posts包含博客,并常常检索author_name字段且此字段需排序,那么可经过建立author_name字段上的索引来提升性能:
db.posts.createIndex( { author_name : 1 } )
例子
若是常常检索timestamp 字段且此字段需排序,那么可经过建立timestamp 字段上的索引来提升性能:
建立索引:
db.posts.createIndex( { timestamp : 1 } )
优化查询:
db.posts.find().sort( { timestamp : -1 } )
由于MongoDB 能按升序或降序读取索引,因此单一键值索引方向可有可无。
索引支持查询、更新操做和聚合管道(aggregation pipeline)的某些阶段。
索引键值是BinData 类型的数据,若是知足下面的条件这样的键值会更高效地存储在索引中:
限制返回查询结果数据量以减小网络需求
MongoDB 游标返回成组的文档。若是你知道想要的结果的数量,可使用limit() 方法来减小对网络资源的需求。
这经常和排序操做一块儿用。例如,须要返回10个结果,执行以下命令:
db.posts.find().sort( { timestamp : -1 } ).limit(10)
使用投影器仅返回必要的数据
当你只须要文档字段的子集时,仅返回须要的字段可得到更好的性能:
例如,在你的查询中你只须要timestamp, title, author, 和abstract 字段,执行下面的命令:
Db.posts.find
(
{},
{ timestamp : 1 , title : 1 , author : 1 , abstract : 1}
).sort( { timestamp : -1 })
使用$hint选择一个特定的索引
大多数状况下,查询优化器会为指定操做选择最优的索引。然而可以使用hint()方法强制MongoDB 使用指定索引。使用hint() 支持性能测试,或者用于必须选择一个字段的查询,或者用于必须选择被包含在几个索引中的字段的查询。
使用增量操做符来执行服务端的操做
使用MongoDB 的$inc操做符来增长或者减少文档中的值。增量操做符在服务器端增长字段值,一个可替代的方案是,选择一个文档并在客户端修改它,而后将整个文档写入服务器。
$inc 还可以帮助防止竞态条件,竞态条件能致使当两个应用实例同时查询一个文档时,手动地修改一个字段而后同时将文档写入服务器。
2.6 写操做性能
2.6.1 索引
插入,更新,或者删除操做完成之后,MongoDB 必须更新每个和集合有关的索引,除数据自己之外。所以对于写操做的性能来说,集合中的每个索引都增长了大量的开销。通常来说,索引使读操做性能有所提升,这对插入操做的性能损害是值得的。然而为了提升写操做的性能,建立索引和评估已存在的索引以确保查询能够利用这些索引时要慎重。
对于插入和更新非索引字段,稀疏索引(sparse indexes)比非稀疏索引开销小。对于非稀疏索引,不会改变记录大小的更新操做有更小的索引开销。
2.6.2 文档规模变大和MMAPv1存储引擎
某些更新操做会使文档变大;例如,向文档中添加一个字段。
对于MMAPv1 存储引擎,若是更新操做使得一个文档超过了当前已分配的大小,那么为了保存文档,MongoDB 会从新定位文档使其得到足够的连续磁盘空间。须要重定位的更新比不须要重定位的更新更耗时,特别是对于有索引的集合。若是集合有索引,MongoDB 必须更新全部索引条目。所以,对于有大量索引的集合而言,这个动做影响了写操做的吞吐量。
3.0.0版本的变化:默认地MongoDB 使用2的幂次大小配额策略来自动地为MMAPv1存储引擎填充。2的幂次大小配额策略确保MongoDB为文档分配的存储空间大小为2的幂,这有助于确保MongoDB 可以有效地重用由删除文档或者重定位文档所释放的空间,同时减小许多状况下从新分配空间的发生。
尽管 2的幂次大小配额策略减小了从新分配空间的发生,但并无消除为文档从新分配空间。
2.6.3存储性能
硬件
存储系统的容量对MongoDB 写操做的性能产生了一些重要的物理限制。与驱动器存储系统有关的许多独特因素影响了写操做性能,包括随机访问模式,磁盘高速缓存,磁盘预读和RAID配置。
对于随机任务负载,固态硬盘(SSDs)的性能比机械硬盘(HDDs)的性能好100倍以上。
日志
为了在事故发生时提供持久性,MongoDB 采用预写日志策略将日志写入磁盘。MongoDB 首先将内存变化写入磁盘日志文件。若是在将变动写入磁盘数据文件以前,MongoDB应该终止或遇到错误,MongoDB可以使用日志文件来执行写操做将变动数据写入磁盘。
日志提供的持久性保障一般比额外的写操做带来的性能损耗重要,考虑下面的日志和性能之间的相互影响:
2.7解释结果
3.0版本中的变化
MongoDB 提供db.collection.explain()方法,cursor.explain()方法,explain命令来得到关于查询方案和查询方案执行状态的信息。解释结果将查询方案展示为一颗阶段树。每一阶段将结果(例如文档或索引键)传递给父节点。叶节点使用集合或索引。内部节点操做来自子节点的文档或索引键。根节点是MongoDB提供的结果集中的最终阶段。
每一个阶段都是操做的描述;例如:
2.7.1解释输出
下面展现了由explain 操做返回的一系列关键字段。
注
queryPlanner
queryPlanner信息清晰地说明了查询优化程序所选择的方案。
对于非分片集合,explain 返回的信息以下:
{
"queryPlanner" : {
"plannerVersion" : <int>,
"namespace" : <string>,
"indexFilterSet" : <boolean>,
"parsedQuery" : {
...
},
"winningPlan" : {
"stage" : <STAGE1>,
...
"inputStage" : {
"stage" : <STAGE2>,
...
"inputStage" : {
...
}
}
},
"rejectedPlans" : [
<candidate plan 1>,
...
]
}
explain.queryPlanner
包含了关于查询优化程序所选择的方案信息。
explain.queryPlanner.namespace
一个字符串,指明查询运行在其中的命名空间(例如,<database>.<collection>)。
explain.queryPlanner.indexFilterSet
一个布尔值,指明MongoDB 是否为查询模型使用索引过滤器。
explain.queryPlanner.winningPlan
一个文档,清晰地说明了查询优化程序所选择的方案。MongoDB 以阶段树的形式展现这个方案。例如,一个阶段有一个inputStage,或者这个阶段有多个子阶段,那么这个阶段有多个inputStage。
explain.queryPlanner.winningPlan.stage
一个字符串,表示阶段的名称。
每一个阶段包含了各自的具体信息。例如,IXSCAN 阶段包含了索引界限以及索引扫描数据。若是一个阶段有一个或多个子阶段,那么这个阶段将会有一个或多个inputStage。
explain.queryPlanner.winningPlan.inputStage
描述子阶段的文档,这个子阶段为它的父节点提供文档和索引键。若是父阶段只有一个子阶段,那么此字段就存在。
explain.queryPlanner.winningPlan.inputStages
描述多个子阶段的文档数组。这些子阶段为它们的父节点提供文档和索引键。若是父阶段有多个子阶段,那么此字段存在。例如,对于$or表达式或索引交叉策略来讲,阶段有多个输入源。
explain.queryPlanner.rejectedPlans
被查询优化程序考虑的和拒绝的备选方案构成的数组。若是没有其余的备选方案,那么这个集合是空的。
对于分片集合,获胜方案包括分片数组,这个数组包含每个可访问分片的方案信息。
executionStats
返回的executionStats 信息详细描述了获胜方案的执行状况。
为了使executionStats 存在于结果中,必须以executionStats 或allPlansExecution模式来运行explain 命令。
为了包含在方案筛选阶段捕获的部分运行数据,必须使用allPlansExecution模式。
对于非分片集合,explain 返回下列信息:
"executionStats" : {
"executionSuccess" : <boolean>,
"nReturned" : <int>,
"executionTimeMillis" : <int>,
"totalKeysExamined" : <int>,
"totalDocsExamined" : <int>,
"executionStages" : {
"stage" : <STAGE1>
"nReturned" : <int>,
"executionTimeMillisEstimate" : <int>,
"works" : <int>,
"advanced" : <int>,
"needTime" : <int>,
"needYield" : <int>,
"isEOF" : <boolean>,
...
"inputStage" : {
"stage" : <STAGE2>,
...
"nReturned" : <int>,
"executionTimeMillisEstimate" : <int>,
"keysExamined" : <int>,
"docsExamined" : <int>,
...
"inputStage" : {
...
}
}
},
"allPlansExecution" : [
{ <partial executionStats1> },
{ <partial executionStats2> },
...
]
}
explain.executionStats
包含统计数据,描述了按获胜方案实施的完整的查询操做执行状况。对于写操做来讲,完整的查询操做执行状况涉及到了可能已经执行了的修改操做,但并无将修改应用到数据库。
explain.executionStats.nReturned
匹配查询条件的文档的数量。nReturned对应n,n为MongoDB早期版本中的cursor.explain()方法返回字段。
explain.executionStats.executionTimeMillis
筛选查询方案和执行查询所需的以毫秒为单位的时间总和。executionTimeMillis 对应millis ,millis 为MongoDB早期版本中的
cursor.explain()方法返回字段。
explain.executionStats.totalKeysExamined
被扫描的索引条目数量。totalKeysExamined 对应nscanned ,nscanned 为MongoDB早期版本中的cursor.explain()方法返回字段。
explain.executionStats.totalDocsExamined
被扫描的文档数量。totalDocsExamined 对应nscannedObjects ,nscannedObjects 为MongoDB早期版本中的cursor.explain()方法返回字段。
explain.executionStats.executionStages
用阶段树表示的获胜方案的完整执行过程的详细描述。
例如,一个阶段能够有一个或多个inputStage。
explain.executionStats.executionStages.works
表示查询执行阶段涉及的工做单元数量。查询执行将一份工做分配到多个小的单元中。一个工做单元由审查一个索引键,获取集合中的一个文档,对一个文档使用一个投影器,或由完成一块内部记帐构成。
explain.executionStats.executionStages.advanced
返回的中间结果的数量或由本阶段到它的父阶段的距离。
explain.executionStats.executionStages.needTime
不经过中间结果到达父阶段的工做周期数。例如,一个索引扫描阶段可能须要一个工做周期探寻到索引中的一个新位置而不是返回索引键;这个工做周期被计入explain.executionStats.executionStages.needTime中而不是explain.executionStats.executionStages.advanced中。
explain.executionStats.executionStages.needYield
存储层所需的查询系统退出自身锁的次数。
explain.executionStats.executionStages.isEOF
指示执行阶段是否已到达流结尾处。
IXSCAN的input stage阶段。若是查询不只返回指定的限量,LIMIT 阶段会报告isEOF: 1,但LIMIT 阶段下层的IXSCAN 阶段会报告isEOF: 0。
explain.executionStats.executionStages.inputStage.keysExamined
对于扫描索引的查询执行阶段,keysExamined是在索引扫描过程当中检测到的界内或界外的键值总数。若是索引扫描包含一段连续的键值,仅界内的键值须要被检测。若是索引扫描包含几段连续的键值,索引扫描过程可能会检测界外键值,为了从一段的末尾调到下一段的开始。
考虑下面的例子,有一个索引字段x,集合中包含100个文档,其中x为从1到100。
db.keys.find( { x : { $in : [ 3, 4, 50, 74, 75, 90 ] } } ).explain( "executionStats" )
查询将扫描键3和4,而后将会扫描键5,检测到5在界外,而后跳到下一个键50。
继续这个过程,查询扫描键3, 4, 5, 50, 51, 74, 75, 76, 90, 和91。键5, 51, 76, and 91是界外键,它们仍会被检测。keysExamined 值为10。
explain.executionStats.executionStages.inputStage.docsExamined
指明查询执行阶段扫描的文档数量。
目前适用于COLLSCAN 阶段和在集合中检索文档的阶段(例如FETCH)。
explain.executionStats.allPlansExecution
在方案选择阶段关于获胜方案和被拒绝方案的部分执行信息。这个字段仅存在于以allPlansExecution 模式执行的explain命令的返回结果中。
serverInfo
对于非分片集合,explain 返回下列信息:
"serverInfo" : {
"host" : <string>,
"port" : <int>,
"version" : <string>,
"gitVersion" : <string>
}
2.7.2分片集合
对于分片集合,explain 返回核心查询方案和每个可访问分片的服务器信息,信息被保存在shards 字段中。
{
"queryPlanner" : {
...
"winningPlan" : {
...
"shards" : [
{
"shardName" : <shard>,
<queryPlanner information for shard>,
<serverInfo for shard>
},
...
],
},
},
"executionStats" : {
...
"executionStages" : {
...
"shards" : [
{
"shardName" : <shard>,
<executionStats for shard>
},
...
]
},
"allPlansExecution" : [
{
"shardName" : <string>,
"allPlans" : [ ... ]
},
...
]
}}
explain.queryPlanner.winningPlan.shards
每个可用分片的包含了queryPlanner和serverInfo的文档数组。
explain.executionStats.executionStages.shards
每个可用分片的包含了executionStats 的文档数组。
2.7.3兼容性变化
3.0版本的变化
explain 结果的样式和字段与老版本不一样。下面列举了一些关键的不一样点:
集合扫描与索引的使用
若是查询方案规划者选择扫描一个集合,那么解释结果包含一个COLLSCAN 阶段。
若是查询方案规划者选择一个索引,解释结果包含一个IXSCAN 阶段。这个阶段包含一些信息,例如索引键模式,遍历的方向,索引界限。
MongoDB之前的版本中,cursor.explain() 返回字段cursor,其值为:
覆盖查询
当一个索引覆盖一个查询时,MongoDB可以仅利用这个索引键(许多个键)匹配查询条件并返回结果。例如,MongoDB不须要检测来自集合中的文档而返回结果。
当一个索引覆盖一个查询时,解释结果包含了IXSCAN阶段,这个阶段不是由FETCH阶段衍生的,而且在executionStats中,
totalDocsExamined的值为0。
MongoDB之前的版本中,cursor.explain()返回indexOnly字段,指明这个索引是否覆盖一个查询。
索引交叉
对于索引交叉方案,结果会包含AND_SORTED阶段或者AND_HASH阶段和详细描述索引的inputStages数组。
{
"stage" : "AND_SORTED",
"inputStages" : [
{
"stage" : "IXSCAN",
...
},
{
"stage" : "IXSCAN",
...
}
]}
MongoDB以前的版本中, cursor.explain()返回cursor字段,cursor字段的值为索引交叉复平面的值。
$or 表达式
若是MongoDB 为$or表达式使用索引,那么结果将会包含OR阶段,连同详细,描述索引的inputStages数组。
{
"stage" : "OR",
"inputStages" : [
{
"stage" : "IXSCAN",
...
},
{
"stage" : "IXSCAN",
...
},
...
]}
MongoDB之前的版本中,cursor.explain()返回的结果中clauses数组详细描述了索引。
Sort阶段
若是MongoDB可以使用索引扫描来得到所需的排序顺序,那么结果不会包含SORT阶段。不然MongoDB不使用索引扫描来得到所需的排序顺序,那么结果将包含SORT阶段。
MongoDB之前的版本中,cursor.explain()返回的结果中scanAndOrder字段指明MongoDB是否使用索引扫描来得到所需的排序顺序。
2.8分析查询性能
cursor.explain("executionStats") 和db.collection.explain("executionStats")提供了关于查询性能的统计信息。这些数据对于测量是否以及如何使用索引是有帮助的。
db.collection.explain()提供了其余操做的执行信息,例如,db.collection.update()。
2.8.1 评估一个查询的性能
考虑集合inventory:
{ "_id" : 1, "item" : "f1", type: "food", quantity: 500 }
{ "_id" : 2, "item" : "f2", type: "food", quantity: 100 }
{ "_id" : 3, "item" : "p1", type: "paper", quantity: 200 }
{ "_id" : 4, "item" : "p2", type: "paper", quantity: 150 }
{ "_id" : 5, "item" : "f3", type: "food", quantity: 300 }
{ "_id" : 6, "item" : "t1", type: "toys", quantity: 500 }
{ "_id" : 7, "item" : "a1", type: "apparel", quantity: 250 }
{ "_id" : 8, "item" : "a2", type: "apparel", quantity: 400 }
{ "_id" : 9, "item" : "t2", type: "toys", quantity: 50 }
{ "_id" : 10, "item" : "f4", type: "food", quantity: 75 }
不使用索引的查询
查询集合中的文档,查询条件为quantity值在100到200之间。
db.inventory.find( { quantity: { $gte: 100, $lte: 200 } } )
返回结果为:
{ "_id" : 2, "item" : "f2", "type" : "food", "quantity" : 100 }
{ "_id" : 3, "item" : "p1", "type" : "paper", "quantity" : 200 }
{ "_id" : 4, "item" : "p2", "type" : "paper", "quantity" : 150 }
使用explain("executionStats")来查看所使用的查询方案:
db.inventory.find(
{ quantity: { $gte: 100, $lte: 200 } }).explain("executionStats")
explain()返回结果为:
{
"queryPlanner" : {
"plannerVersion" : 1,
...
"winningPlan" : {
"stage" : "COLLSCAN",
...
}
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 3,
"executionTimeMillis" : 0,
"totalKeysExamined" : 0,
"totalDocsExamined" : 10,
"executionStages" : {
"stage" : "COLLSCAN",
...
},
...
},
...}
检测的文档数与查询匹配到的文档数的不一样指示,为了提升查询性能,使用索引可能会有效果。
使用索引的查询
为quantity字段添加索引:
db.inventory.createIndex( { quantity: 1 } )
使用explain("executionStats")来查看所使用的查询方案:
db.inventory.find(
{ quantity: { $gte: 100, $lte: 200 } }).explain("executionStats")
explain()返回结果为:
{
"queryPlanner" : {
"plannerVersion" : 1,
...
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"quantity" : 1
},
...
}
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 3,
"executionTimeMillis" : 0,
"totalKeysExamined" : 3,
"totalDocsExamined" : 3,
"executionStages" : {
...
},
...
},
...}
当使用索引时,查询扫描了3个索引条目和3个文档而且返回3个文档。不用索引时,查询返回3个匹配到的文档且扫描了整个集合,即10个文档。
2.8.2 比较索引性能
为了手工测试使用了不止一个索引的查询性能,能够与 explain()方法一块儿使用hint()方法。
考虑下面的查询:
db.inventory.find( { quantity: { $gte: 100, $lte: 300 }, type: "food" } )
查询返回值为:
{ "_id" : 2, "item" : "f2", "type" : "food", "quantity" : 100 }
{ "_id" : 5, "item" : "f3", "type" : "food", "quantity" : 300 }
为了支持查询,添加复合索引。有了复合索引,字段的顺序是有意义的。
例如,增长下面两个复合索引。
第一个索引使quantity处在第一位,type排在第二位。第二个索引使type处在第一位,quantity排在第二位。
db.inventory.createIndex( { quantity: 1, type: 1 } )
db.inventory.createIndex( { type: 1, quantity: 1 } )
评估第一个索引的效果:
db.inventory.find(
{ quantity: { $gte: 100, $lte: 300 }, type: "food" }).hint({ quantity: 1, type: 1 }).explain("executionStats")
explain()返回的结果为:
{
"queryPlanner" : {
...
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"quantity" : 1,
"type" : 1
},
...
}
}
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 2,
"executionTimeMillis" : 0,
"totalKeysExamined" : 5,
"totalDocsExamined" : 2,
"executionStages" : {
...
}
},
...}
扫描了5个索引键(executionStats.totalKeysExamined) 返回2个文档(executionStats.nReturned)。
评估第二个索引的效果:
db.inventory.find(
{ quantity: { $gte: 100, $lte: 300 }, type: "food" }).hint({ type: 1, quantity: 1 }).explain("executionStats")
explain()返回的结果为:
{
"queryPlanner" : {
...
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"type" : 1,
"quantity" : 1
},
...
}
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 2,
"executionTimeMillis" : 0,
"totalKeysExamined" : 2,
"totalDocsExamined" : 2,
"executionStages" : {
...
}
},
...}
扫描了2个索引键(executionStats.totalKeysExamined) 返回2个文档(executionStats.nReturned)。
对于这个例子,复合索引{ type: 1, quantity: 1 }比{ quantity: 1, type: 1 }更高效。
2.9 Tailable游标
默认地,当客户端遍历完结果集后,MongoDB会自动地关闭游标。对于固定集合,可以使用Tailable游标保持游标打开,当客户端遍历完最初的结果集后。从概念上讲,Tailable游标等价于带有-f选项的Unix tail命令(例如使用follow模式)。客户端向集合中插入新文档后,tailable 游标仍然会继续检索文档。
在固定集合上使用tailable游标且有高写通量,索引不是切实可行的。例如,MongoDB使用tailable游标追踪副本集主成员的oplog。
注:
若是查询已索引字段,不要使用tailable游标,要使用regular游标。保持追踪查询返回的索引字段的最终值。为了查询新添加的文档,在查询准则中使用索引字段的最终值,例子以下:
db.<collection>.find( { indexedField: { $gt: <lastvalue> } } )
考虑以下关于tailable游标的行为:
一个消亡的游标id值为0。
-----------------------------------------------------------------------------------------
转载与引用请注明出处。
时间仓促,水平有限,若有不当之处,欢迎指正。