上一篇文章: MongoDB指南---十一、使用複合索引、$操做符如何使用索引、索引對象和數組、索引基數
下一篇文章: MongoDB指南---1三、索引類型
從上面的內容能夠看出,explain()可以提供大量與查詢相關的信息。對於速度比較慢的查詢來講,這是最重要的診斷工具之一。經過查看一個查詢的explain()輸出信息,能夠知道查詢使用了哪一個索引,以及是如何使用的。對於任意查詢,均可以在最後添加一個explain()調用(與調用sort()或者limit()同樣,不過explain()必須放在最後)。
最多見的explain()輸出有兩種類型:使用索引的查詢和沒有使用索引的查詢。對於特殊類型的索引,生成的查詢計劃可能會有些許不一樣,可是大部分字段都是類似的。另外,分片返回的是多個explain()的聚合(第13章會介紹),由於查詢會在多個服務器上執行。
不使用索引的查詢的exlpain()是最基本的explain()類型。若是一個查詢不使用索引,是由於它使用了"BasicCursor"(基本遊標)。反過來講,大部分使用索引的查詢使用的是BtreeCursor(某些特殊類型的索引,好比地理空間索引,使用的是它們本身類型的遊標)。
對於使用了複合索引的查詢,最簡單狀況下的explain()輸出以下所示:數據庫
> db.users.find({"age" : 42}).explain() { "cursor" : "BtreeCursor age_1_username_1", "isMultiKey" : false, "n" : 8332, "nscannedObjects" : 8332, "nscanned" : 8332, "nscannedObjectsAllPlans" : 8332, "nscannedAllPlans" : 8332, "scanAndOrder" : false, "indexOnly" : false, "nYields" : 0, "nChunkSkips" : 0, "millis" : 91, "indexBounds" : { "age" : [ [ 42, 42 ] ], "username" : [ [ { "$minElement" : 1 }, { "$maxElement" : 1 } ] ] }, "server" : "ubuntu:27017" }
從輸出信息中能夠看到它使用的索引是age_1_username_1。"millis"代表了這個查詢的執行速度,時間是從服務器收到請求開始一直到發出響應爲止。然而,這個數值不必定真的是你但願看到的值。若是MongoDB嘗試了多個查詢計劃,那麼"millis"顯示的是這些查詢計劃花費的總時間,而不是最優查詢計劃所花的時間。
接下來是實際返回的文檔數量:"n"。它沒法反映出MongoDB在執行這個查詢的過程當中所作的工做:搜索了多少索引條目和文檔。索引條目是使用"nscanned"描述的。"nscannedObjects"字段的值就是所掃描的文檔數量。最後,若是要對結果集進行排序,而MongoDB沒法對排序使用索引,那麼"scanAndOrder"的值就會是true。也就是說,MongoDB不得不在內存中對結果進行排序,這是很是慢的,並且結果集的數量要比較小。
如今你已經知道這些基礎知識了,接下來依次詳細介紹這些字段。ubuntu
BtreeCursor表示本次查詢使用了索引,具體來講,是使用了"age"和"username"上的索引{"age" : 1, "username" : 1}。若是查詢要對結果進行逆序遍歷,或者是使用了多鍵索引,就能夠在這個字段中看到"reverse"和"multi"這樣的值。segmentfault
用於說明本次查詢是否使用了多鍵索引(詳見5.1.4節)。數組
本次查詢返回的文檔數量。緩存
這是MongoDB按照索引指針去磁盤上查找實際文檔的次數。若是查詢包含的查詢條件不是索引的一部分,或者說要求返回不在索引內的字段,MongoDB就必須依次查找每一個索引條目指向的文檔。服務器
若是有使用索引,那麼這個數字就是查找過的索引條目數量。若是本次查詢是一次全表掃描,那麼這個數字就表示檢查過的文檔數量。工具
MongoDB是否在內存中對結果集進行了排序。優化
MongoDB是否只使用索引就能完成這次查詢(詳見「覆蓋索引」部分)。
在本例中,MongoDB只使用索引就找到了所有的匹配文檔,從"nscanned"和"n"相等就能夠看出來。然而,本次查詢要求返回匹配文檔中的全部字段,而索引只包含"age"和"username"兩個字段。若是將本次查詢修改成({"_id" : 0, "age" : 1, "username" : 1}),那麼本次查詢就能夠被索引覆蓋了,"indexOnly"的值就會是true。指針
爲了讓寫入請求可以順利執行,本次查詢暫停的次數。若是有寫入請求須要處理,查詢會週期性地釋放它們的鎖,以便寫入可以順利執行。然而,在本次查詢中,沒有寫入請求,由於查詢沒有暫停過。code
數據庫執行本次查詢所耗費的毫秒數。這個數字越小,說明查詢效率越高。
這個字段描述了索引的使用狀況,給出了索引的遍歷範圍。因爲查詢中的第一個語句是精確匹配,所以索引只須要查找42這個值就能夠了。本次查詢沒有指定第二個索引鍵,所以這個索引鍵上沒有限制,數據庫會在"age"爲42的條目中將用戶名介於負無窮("$minElement" : 1)和正無窮("$maxElement" : 1)的條目都找出來。
再來看一個稍微複雜點的例子:假若有一個{"user name" : 1, "age" : 1}上的索引和一個 {"age" : 1, "username" : 1}上的索引。同時查詢"username"和"age"時,會發生什麼狀況?呃,這取決於具體的查詢:
> db.c.find({age : {$gt : 10}, username : "sally"}).explain() { "cursor" : "BtreeCursor username_1_age_1", "indexBounds" : [ [ { "username" : "sally", "age" : 10 }, { "username" : "sally", "age" : 1.7976931348623157e+308 } ] ], "nscanned" : 13, "nscannedObjects" : 13, "n" : 13, "millis" : 5 }
因爲在要在"username"上執行精確匹配,在"age"上進行範圍查詢,所以,數據庫選擇使用{"username" : 1, "age" : 1}索引,這與查詢語句的順序相反。另外一方面來講,若是須要對"age"精確匹配而對"username"進行範圍查詢,MongoDB就會使用另外一個索引:
> db.c.find({"age" : 14, "username" : /.*/}).explain() { "cursor" : "BtreeCursor age_1_username_1 multi", "indexBounds" : [ [ { "age" : 14, "username" : "" }, { "age" : 14, "username" : { } } ], [ { "age" : 14, "username" : /.*/ }, { "age" : 14, "username" : /.*/ } ] ], "nscanned" : 2, "nscannedObjects" : 2, "n" : 2, "millis" : 2 }
若是發現MongoDB使用的索引與本身但願它使用的索引不一致,可使用hit()強制MongoDB使用特定的索引。例如,若是但願MongoDB在上個例子的查詢中使用{"username" : 1, "age" : 1}索引,能夠這麼作:
> db.c.find({"age" : 14, "username" : /.*/}).hint({"username" : 1, "age" : 1})
若是查詢沒有使用你但願它使用的索引,因而你使用hint強制MongoDB使用某個索引,那麼應該在應用程序部署以前在所指定的索引上執行explain()。若是強制MongoDB在某個查詢上使用索引,而這個查詢不知道如何使用這個索引,這樣會致使查詢效率下降,還不如不使用索引來得快。
MongoDB的查詢優化器與其餘數據庫稍有不一樣。基原本說,若是一個索引可以精確匹配一個查詢(要查詢"x",恰好在"x"上有一個索引),那麼查詢優化器就會使用這個索引。否則的話,可能會有幾個索引都適合你的查詢。MongoDB會從這些可能的索引子集中爲每次查詢計劃選擇一個,這些查詢計劃是並行執行的。最先返回100個結果的就是勝者,其餘的查詢計劃就會被停止。
這個查詢計劃會被緩存,這個查詢接下來都會使用它,直到集合數據發生了比較大的變更。若是在最初的計劃評估以後集合發生了比較大的數據變更,查詢優化器就會從新挑選可行的查詢計劃。創建索引時,或者是每執行1000次查詢以後,查詢優化器都會從新評估查詢計劃。
explain()輸出信息裏的"allPlans"字段顯示了本次查詢嘗試過的每一個查詢計劃。
提取較小的子數據集時,索引很是高效。也有一些查詢不使用索引會更快。結果集在原集合中所佔的比例越大,索引的速度就越慢,由於使用索引須要進行兩次查找:一次是查找索引條目,一次是根據索引指針去查找相應的文檔。而全表掃描只須要進行一次查找:查找文檔。在最壞的狀況下(返回集合內的全部文檔),使用索引進行的查找次數會是全表掃描的兩倍,效率會明顯比全表掃描低不少。
惋惜,並無一個嚴格的規則能夠告訴咱們,如何根據數據大小、索引大小、文檔大小以及結果集的平均大小來判斷何時索引頗有用,何時索引會下降查詢速度(如表5-1所示)。通常來講,若是查詢須要返回集合內30%的文檔(或者更多),那就應該對索引和全表掃描的速度進行比較。然而,這個數字可能會在2%~60%之間變更。
表5-1 影響索引效率的屬性
索引一般適用的狀況 | 全表掃描一般適用的狀況 |
---|---|
集合較大 | 集合較小 |
文檔較大 | 文檔較小 |
選擇性查詢 | 非選擇性查詢 |
假如咱們有一個收集統計信息的分析系統。應用程序要根據給定帳戶去系統中查詢全部文檔,根據從初始一直到一小時以前的數據生成圖表:
> db.entries.find({"created_at" : {"$lt" : hourAgo}})
咱們在"created_at"上建立索引以提升查詢速度。
最初運行時,結果集很是小,能夠當即返回。幾個星期過去之後,數據開始多起來了,一個月以後,這個查詢耗費的時間愈來愈長。
對於大部分應用程序來講,這極可能就是那個「錯誤的」查詢:真的須要在查詢中返回數據集中的大部份內容嗎?大部分應用程序(尤爲是擁有很是大的數據集的應用程序)都不須要。然而,也有一些合理的狀況,可能須要獲得大部分或者所有的數據:也許須要將這些數據導出到報表系統,或者是放在批量任務中。在這些狀況下,應該儘量快地返回數據集中的內容。
能夠用{"$natural" : 1}強制數據庫作全表掃描。6.1節會介紹$natural,它能夠指定文檔按照磁盤上的順序排列。特別地,$natural能夠強制MongoDB作全表掃描:
> db.entries.find({"created_at" : {"$lt" : hourAgo}}).hint({"$natural" : 1})
使用"$natural"排序有一個反作用:返回的結果是按照磁盤上的順序排列的。對於一個活躍的集合來講,這是沒有意義的:隨着文檔體積的增長或者縮小,文檔會在磁盤上進行移動,新的文檔會被寫入到這些文檔留下的空白位置。可是,對於只須要進行插入的工做來講,若是要獲得最新的(或者最先的)文檔,使用$natural就很是有用了。
上一篇文章: MongoDB指南---十一、使用複合索引、$操做符如何使用索引、索引對象和數組、索引基數
下一篇文章: MongoDB指南---1三、索引類型