1. 程式人生 > 其它 >【翻譯】MongoDB指南/CRUD操作(四)

【翻譯】MongoDB指南/CRUD操作(四)

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

CRUD操作(四)

1 查詢方案(Query Plans)

MongoDB 查詢優化程式處理查詢並且針對給定可利用的索引選擇最有效的查詢方案。然後每次執行查詢時,查詢系統使用此查詢方案。

查詢優化程式僅快取可能有多種切實可行的方案的查詢計劃。

對於每一個查詢,查詢規劃者在查詢方案快取記憶體中搜索適合查詢形式的查詢方案。如果沒有匹配的查詢方案,查詢規劃者生成幾個備選方案並在一個實驗週期內做出評估。查詢規劃者選擇獲勝的方案,建立包含獲勝方案的快取記憶體條目,並使用它獲得查詢結果文件。

如果匹配條目存在,查詢規劃者基於匹配條目生成一個方案,通過重新規劃方案機制評估此方案的效能。這個機制會根據此查詢方案的效能做出通過或否決的決定並保持或者剔除此查詢方案。如果此方案被剔除,那麼查詢計劃會使用一般規劃程序選擇一個新方案並快取它。查詢規劃者執行這個方案並返回查詢結果。

下面這個圖說明了查詢規劃者的處理邏輯:

你可以使用db.collection.explain()或者cursor.explain()方法檢視給定查詢的查詢方案。這些資訊有助於定出索引策略。

db.collection.explain() 提供了其他操作的執行資訊,例如db.collection.update()操作。

查詢方案快取記憶體重新整理

像索引或者刪除集合這樣的目錄操作會重新整理查詢方案快取記憶體。

如果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階段含有

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,其值為:

  • 集合掃描中的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值在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",
         ...
      },
      ...
   },
   ...}
  • 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。

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

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