【翻譯】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。

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

轉載與引用請註明出處。

時間倉促,水平有限,若有不當之處,歡迎指正。

相關文章
相關標籤/搜索