【原文地址】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。
-----------------------------------------------------------------------------------------
轉載與引用請註明出處。
時間倉促,水平有限,若有不當之處,歡迎指正。