(如有打擾,請忽略)阿里雲ECS大羊群,2U4G低至1.4折,限實名新用戶,需要的點吧https://promotion.aliyun.com/ntms/act/vm/aliyun-group/team.html?group=YrliaeMVUn
數據庫索引與書籍的索引類似,有了索引就不需要翻整本書,數據庫可以直接在索引中查找,在索引中找到條目后,就可以直接跳到目標文檔的位置,這可以讓查找的速度提高幾個數量級。
一、創建索引
我們在person這個集合的age鍵上創建一個索引,比較一下創建索引前后,一個查詢的語句的性能區別。
創建索引:db.person.ensureIndex({"age":1})。這里我們使用了ensureIndex在age上建立了索引。“1”:表示按照age進行升序,“-1”:表示按照age進行降序。
沒有索引的查詢性能:
有索引的查詢性能:
我們主要來看這幾個參數,(參數說明,請看上一篇文章)
executionTimeMillis(這次query整體的耗時):無索引耗時962毫秒 ;有索引耗時143毫秒。
totalDocsExamined(文檔掃描條目):無索引是200萬條;有索引是2000條。
stage(查詢的類型):無索引是COLLSCAN(全表掃描);有索引是FETCH+IXSCAN(索引掃描+根據索引去檢索指定document)。
executionStages.executionTimeMillisEstimate(檢索document獲得數據的耗時):無索引耗時910毫秒;有索引耗時0毫秒。
建好索引后,這個query整體的速度提高了1個數量級 (1個數量級是10倍的意思)。根據查詢語句的不同,索引可以使速度提高幾個數量級。
二、復合索引
在多個鍵上建立的索引就是復合索引,有時候我們的查詢不是單條件的,可能是多條件,比如查找年齡在20~30名字叫‘ryan1’的同學,那么我們可以建立“age”和“name”的聯合索引來加速查詢。
為了演示索引的效果,我們來重新生成插入一份200萬個文檔的集合。
我們可以用hint()方法來強制查詢走哪個索引。
我們來看一下,當查詢條件是多個的時候,復合索引相比單鍵索引的強大魅力。
db.person.find({"age":{"$gte":20,"$lte":30},"name":"ryan1"}).hint({"age":1}).explain("executionStats");
1 { 2 ... 3 "executionStats" : { 4 "executionSuccess" : true, 5 "nReturned" : 2000, 6 "executionTimeMillis" : 2031, 7 "totalKeysExamined" : 2000000, 8 "totalDocsExamined" : 2000000, 9 ... 10 }
db.person.find({"age":{"$gte":20,"$lte":30},"name":"ryan1"}).hint({"age":1,"name":1}).explain("executionStats");
1 { 2 ... 3 "executionStats" : { 4 "executionSuccess" : true, 5 "nReturned" : 2000, 6 "executionTimeMillis" : 8, 7 "totalKeysExamined" : 2010, 8 "totalDocsExamined" : 2000, 9 ... 10 }
從executionTimeMillis的值上,一眼就可以看出卻別。單間索引耗費了2031毫秒,復合索引用了8毫秒。 由此我們可以看出,根據查詢語句的不同,建立正確的索引是非常重要的,對於查詢語句中是多條件的,應多考慮復合索引的應用。
下面,我們再說一種復合索引的重要應用情況。有對一個鍵排序並只要前100個結果的情景(實際項目中經常都是這種情景)。對於這種情況,索引應該這樣建{"sortKey":1,"queryCriteria":1},排序的鍵應該放在復合索引的第一位。
db.person.find({"age":{"$gte":21.0,"$lte":30.0}}).sort({"name":1}).limit(100).hint({"age":1,"name":1}).explain("executionStats");
1 { 2 ... 3 "executionStats" : { 4 "executionSuccess" : true, 5 "nReturned" : 100, 6 "executionTimeMillis" : 6882, 7 "totalKeysExamined" : 1800000, 8 "totalDocsExamined" : 1800000, 9 ... 10 }
db.person.find({"age":{"$gte":21.0,"$lte":30.0}}).sort({"name":1}).limit(100).hint({"name":1,"age":1}).explain("executionStats");
1 { 2 ... 3 "executionStats" : { 4 "executionSuccess" : true, 5 "nReturned" : 100, 6 "executionTimeMillis" : 3, 7 "totalKeysExamined" : 2100, 8 "totalDocsExamined" : 2100, 9 ... 10 }
從上面的結果,我們很容易看出,基於排序鍵的索引,效果非常好。
分析:第一種索引,需要找到所有復合查詢條件的值(依據索引,鍵和文檔可以快速找到),但是找到后,需要對文檔在內存中進行排序,這個步驟消耗了非常多的時間。第二種索引,效果非常好,因為不需要在內存中對大量數據進行排序。但是,MongoDB不得不掃描整個索引以便找到所有文檔。因此,如果對查詢結果的范圍做了限制,那么MongoDB在幾次匹配之后就可以不再掃描索引,在這種情況下,將排序鍵放在第一位是一個非常好的策略。
三、唯一索引
唯一索引可以確保集合的每個文檔的指定鍵都有唯一值。如果想保證不同文檔的“name”鍵擁有不同的值,在“name”鍵上創建一個唯一索引就可以了。
db.person.ensureIndex({"name":1},{"unique":true});
然后用db.person.getIndexes()命令,查看目前person集合所有的索引。
也可以創建復合的唯一索引。創建復合唯一索引時,單個鍵的值可以相同,但所有鍵的組合值必須是唯一的。
db.person.ensureIndex({"name":1,"age":1},{"unique":true});
四、稀疏索引
唯一索引會把null看作值,所以無法將多個缺少唯一索引中的鍵的文檔插入到集合中。然而,在有些情況下,你可能希望唯一索引只對包含相應鍵的文檔生效。這個時候我們可以用到MongoDB中的稀疏索引。該索引與關系型數據庫中的稀疏索引是完全不同的概念。MongoDB中的稀疏索引只是不需要將每個文檔都作為索引條目。
比如,如果有一個可選的mobilephone字段,但是,如果提供了這個字段,那么它的值必須是唯一的:
db.person.ensureIndex({"mobilephone":1}{"unique":true,"sparse":true});
稀疏索引不必是唯一的。只要去掉unique選項,就可以創建一個非唯一的稀疏索引。
五、索引管理
如第一小節所述,可以使用ensureIndex方法創建新的索引,也可以使用createIndex方法。
創建一個索引之后,可以利用getIndexes()方法來查看給定集合上的所有索引的信息。
db.person.getIndexes();
隨着業務的不斷變化,你可能會發現數據或者查詢已經發生了改變,原來的索引也不那么好用了。這時可以使用dropIndex()方法刪除不需要的索引:
db.person.dropIndex("name_1");//刪除索引名為name_1的索引。