【翻译】MongoDB指南/CRUD操做(四)

【原文地址】https://docs.mongodb.com/manual/git

CRUD操做(四)正则表达式

1 查询方案(Query Plansmongodb

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() 方法,因此谨慎地使用索引过滤器。

2 查询优化(Query Optimization

经过减小读操做处理的数据量,索引改进了读操做的效率。这简化了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 类型的数据,若是知足下面的条件这样的键值会更高效地存储在索引中:

  • 二进制子类型值为0-7 128-135
  • 字节数组的长度是0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 32

限制返回查询结果数据量以减小网络需求

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可以使用日志文件来执行写操做将变动数据写入磁盘。

日志提供的持久性保障一般比额外的写操做带来的性能损耗重要,考虑下面的日志和性能之间的相互影响:

  • 若是日志和数据文件在同一块设备上,数据文件和日志可能不得不为得到有限数量的可用I/O资源而竞争。把日志移到单独的设备上可能会增长写操做的能力。
  • 若是应用指定的write concerns包含j选项,mongod 会减小日志写入之间的持续时间,这在总体上增长了写操做的负担。
  • 日志写操做之间的持续时间能够经过运行时选项commitIntervalMs来配置。减小日志写操做之间的持续时间会增长写操做的次数,这会限制MongoDB写操做的能力。增长日志写操做之间的持续时间会减小总的写操做的次数,但也加大了发生错误时没有记录写操做的机会

 

2.7解释结果

3.0版本中的变化

MongoDB 提供db.collection.explain()方法,cursor.explain()方法,explain命令来得到关于查询方案和查询方案执行状态的信息。解释结果将查询方案展示为一颗阶段树。每一阶段将结果(例如文档或索引键)传递给父节点。叶节点使用集合或索引。内部节点操做来自子节点的文档或索引键。根节点是MongoDB提供的结果集中的最终阶段。

每一个阶段都是操做的描述;例如:

  • 扫描集合COLLSCAN
  • 扫描索引键IXSCAN
  • 检索文档FETCH
  • 合并分片结果SHARD_MERGE

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

指示执行阶段是否已到达流结尾处。

  • 若是是true 1,表示执行阶段已到达流末尾。
  • 若是是false 0,此阶段可能仍有结果要返回。例如,考虑带有以下限制的查询:执行的多个阶段包含LIMIT阶段,此LIMIT阶段含有

  IXSCANinput 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

 

每个可用分片的包含了queryPlannerserverInfo文档数组

explain.executionStats.executionStages.shards

每个可用分片的包含了executionStats 文档数组。

2.7.3兼容性变化

3.0版本的变化

explain 结果的样式和字段与老版本不一样。下面列举了一些关键的不一样点:

集合扫描与索引的使用

若是查询方案规划者选择扫描一个集合,那么解释结果包含一个COLLSCAN 阶段。

若是查询方案规划者选择一个索引,解释结果包含一个IXSCAN 阶段。这个阶段包含一些信息,例如索引键模式,遍历的方向,索引界限。

MongoDB之前的版本中,cursor.explain() 返回字段cursor,其值为:

  • 集合扫描中的BasicCursor
  • 索引扫描中的BtreeCursor <index name> [<direction>]

覆盖查询

当一个索引覆盖一个查询时,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值在100200之间。

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",

         ...

      },

      ...

   },

   ...}

  • queryPlanner.winningPlan.stage值为COLLSCAN,代表使用集合扫描。
  • executionStats.nReturned值为3,代表查询获得3个文档。
  • executionStats.totalDocsExamined值为10,代表MongoDB不得不扫描10个文档来得到3个文档。

检测的文档数与查询匹配到的文档数的不一样指示,为了提升查询性能,使用索引可能会有效果。

使用索引的查询

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" : {

            ...

         },

         ...

   },

   ...}

  • queryPlanner.winningPlan.inputStage.stage的值为IXSCAN,代表使用索引。
  • executionStats.nReturned值为3,代表查询获得3个文档。
  • executionStats.totalKeysExamined值为3,代表MongoDB扫描了3个索引条目。
  • executionStats.totalDocsExamined值为3,代表MongoDB扫描了3个文档。

当使用索引时,查询扫描了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游标的行为:

  • tailable游标不会使用索引来返回天然排序的多个文档。
  • 由于tailable游标不使用索引,对于查询来讲,最初的扫描代价较高。可是,初次耗尽游标之后,随后的对新添加文档的检索并不须要付出高昂的代价。
  • tailable游标可能已经消亡或者失效,若是知足下面条件之一:
  • 未匹配到查询结果。
  • 游标返回集合末尾处的文档,随后应用程序删除了该文档。

一个消亡的游标id值为0。

-----------------------------------------------------------------------------------------

转载与引用请注明出处。

时间仓促,水平有限,若有不当之处,欢迎指正。

相关文章
相关标签/搜索