1. 程式人生 > 其它 >MongoDB入門實戰教程(9)

MongoDB入門實戰教程(9)

前面我們學習瞭如何套用常見的設計模式打造合適的模型設計,本篇我們來看看在MongoDB中如何使用索引來提高查詢效率。

1 MongoDB也有索引?

在使用傳統關係型資料庫如MSSQL、MySQL等的時候,我們經常會為table中需要經常查詢的欄位建立index(索引)。那麼,MongoDB作為NoSQL的代表,是否也有索引呢?

答案是:有的。

-- 檢視集合的所有已有索引
db.collectionName.getIndexes()

MongoDB的兩種掃描方式

索引通常能夠極大的提高查詢的效率,如果沒有索引,MongoDB在讀取資料時會和MySQL一樣必須掃描集合中的每個文件並選取那些符合查詢條件的記錄。

在MongoDB中,全集合掃描被稱之為 COLLSCAN,而索引掃描則被稱之為IXSCAN。我們可以在MongoDB中使用類似於MySQL中的explain來檢視執行計劃,判斷該查詢是否是IXSCAN即索引掃描:

db.userinfos.find({name:"張三"}).explain()

和關係型資料庫一樣,掃描全集合的查詢COLLSCAN的效率是非常低的,特別在處理大量的資料時,查詢可以要花費幾十秒甚至幾分鐘,這對網站的效能是非常致命的。

MongoDB索引的資料結構:B樹

我們都知道MySQL InnoDB引擎的索引採用的是B+樹,那麼MongoDB的索引採用的是什麼資料結構呢?

答案是:B樹。

為什麼MySQL採用B+樹,而MongoDB採用B樹呢?

首先,我們需要知道,什麼是B樹,什麼又是B+樹。

(1)B+樹(MySQL等關係型資料庫廣泛採用)

B+樹的兩個明顯特點:

  • 資料只出現在葉子節點(查詢效率高)

  • 所有葉子節點增加了一個鏈指標(便於範圍查詢)

(2)B樹(MongoDB採用)

B樹的兩個明顯特點:

  • 樹內的每個節點都儲存資料

  • 葉子節點之間無指標相鄰

針對上面的B+樹和B樹的特點,我們可以得到以下兩個結論:
(1) B樹的樹記憶體儲資料,因此查詢單條資料的時候,B樹的查詢效率不固定,最好的情況是O(1)。我們可以認為在做單一資料查詢的時候,使用B樹平均效能更好。但是,由於B樹中各節點之間沒有指標相鄰,因此B樹不適合做一些資料遍歷操作。

(2) B+樹的資料只出現在葉子節點上,因此在查詢單條資料的時候,查詢速度非常穩定。因此,在做單一資料的查詢上,其平均效能並不如B樹。但是,B+樹的葉子節點上有指標進行相連,因此在做資料遍歷的時候,只需要對葉子節點進行遍歷即可,這個特性使得B+樹非常適合做範圍查詢。

綜述,基於關係型資料庫的關係模型 和 文件資料庫的文件模型,我們可以知道:MySQL中資料遍歷操作比較多(因為需要多表關聯和範圍查詢),所以用B+樹作為索引結構。而MongoDB是做單一文件查詢比較多(因為內嵌設計不需要多集合關聯且很少範圍查詢),資料遍歷操作比較少,所以用B樹作為索引結構

MongoDB的索引查詢效率

由於B樹/B+樹的工作過程十分複雜,但本質上,它是一個有序的資料結構。

我們可以用一個數組來理解它,假設這裡有一個索引為{a:1}(a升序):

在一個有序的結構上,基於我們學習過的二分查詢法,可以實現一個O(log2(n))的高效搜尋效率。這也可以解釋,為什麼基於索引查詢,在資料量很大的情況下會快很多。

2 MongoDB索引使用

單鍵索引

這是最常見的索引型別,無論是在MySQL還是MongoDB中。

// 為collection建立name的升序索引
db.users.createIndex( { name: 1 } );
// 為collection建立name的降序索引
db.users.createIndex( { name: -1 } );

組合索引

這也是一種常見的索引型別,通過建立組合索引可以在保持索引個數的前提下儘可能覆蓋更多的查詢條件。

例如,在members集合中,假設我們往往需要通過以下幾個條件來組合查詢:

db.members.find({ gender: "F", age: {$gte: 18}}).sort("join_date:1");

那麼,我們就可以針對gender、age和join_date做一個組合索引:

db.members.createIndex({"gender":1,"join_date":1,"age":1});

和MySQL一樣,組合索引具有一個特徵:最左匹配原則。

那麼,這就要求我們在建立組合索引時,需要滿足ESR原則:

(1)精確(Equal)匹配的欄位放在最前面,比如這裡的gender欄位;

(2)排序(sort)欄位放中間,比如這裡的join_date欄位;

(3)範圍(Range)匹配的欄位放在最後面,比如這裡的age欄位;

上面這個ESR原則,同樣適用於MySQL 和 ElasticSearch。

多鍵索引

MongoDB使用多鍵索引來索引儲存在陣列中的內容。

如果索引欄位包含陣列值,MongoDB會為陣列的每個元素建立單獨的索引條目。這些多鍵索引允許查詢通過匹配陣列中的元素來獲取包含陣列的文件。

db.classes.insertMany([
     {
         "classname":"class1",
         "students":[{name:'jack',age:20},
                    {name:'tom',age:22},
                    {name:'lilei',age:25}]
      },
      {
         "classname":"class2",
         "students":[{name:'lucy',age:20},
                    {name:'jim',age:23},
                    {name:'jarry',age:26}]
      }]
  )

-- 建立多鍵索引  
db.classes.createIndex({"students.age":1})

地理位置索引

物聯網場景下的監控資料儲存是MongoDB的一大重要應用場景之一,因此也就催生了一種獨特的索引型別:地理位置索引。

-- 建立索引
db.geo_col.createIndex(
  { location: "2d"} ,
  { min:-20, max: 20 , bits: 10},
  { collation: {locale: "simple"} }
)
-- 查詢
db.geo_col.find(
  { location :
    { $geoWithin :
      { $box : [ [ 1, 1 ] , [ 3, 3 ] ] } 
    } 
  }
)
-- 查詢結果
{ "_id" : ObjectId("5c7e7a6243513eb45bf06125"), "location" : [ 1, 1 ] }
{ "_id" : ObjectId("5c7e7a6643513eb45bf06126"), "location" : [ 1, 2 ] }
{ "_id" : ObjectId("5c7e7a6943513eb45bf06127"), "location" : [ 2, 2 ] }
{ "_id" : ObjectId("5c7e7a6d43513eb45bf06128"), "location" : [ 2, 1 ] }
{ "_id" : ObjectId("5c7e7a7343513eb45bf06129"), "location" : [ 3, 1 ] }
{ "_id" : ObjectId("5c7e7a7543513eb45bf0612a"), "location" : [ 3, 2 ] }
{ "_id" : ObjectId("5c7e7a7743513eb45bf0612b"), "location" : [ 3, 3 ] }

有關地理索引的更多介紹,請參閱2d Index Internals。

全文索引

嗯,Luence、ElasticSearch有全文索引,MongoDB也有!

全文檢索會對每一個詞建立一個索引(也稱為 倒排索引),指明該詞在文章中出現的次數和位置,當用戶查詢時,檢索程式就根據事先建立的索引進行查詢,並將查詢的結果反饋給使用者的檢索方式。

這個過程類似於通過字典中的檢索字表查字的過程。

MongoDB 從 2.4 版本開始支援全文檢索,目前支援15種語言的全文索引。

但是,還沒有支援中文!還沒有支援中文!還沒有支援中文!(重要的事情說三遍)

-- 建立全文索引
db.blogArticles.createIndex({"content" : "text"}

3 常見索引屬性

唯一索引

索引的唯一屬性會導致MongoDB拒絕索引欄位的重複值。

除了唯一約束之外,唯一索引在功能上可與其他MongoDB索引互換。

// 在users的name欄位新增唯一索引
db.users.createIndex({name:1},{unique:true})

部分(區域性)索引

顧名思義,部分索引僅索引符合特定的過濾表示式的集合中的文件。

通過索引集合中的文件子集,部分索引具有較低的儲存要求,減少索引建立和維護的效能成本。

部分索引是稀疏索引功能的超集,應該優先於稀疏索引。

//users集合中age>25的部分新增age欄位索引
db.users.createIndex(
  {age:1},
  {partialFilterExpression: {age:{$gt: 25}}}
)
//查詢age<25的document時,因為age<25的部分沒有索引,會全表掃描查詢(stage:COLLSCAN)
db.users.find({age:23})
//查詢age>25的document時,因為age>25的部分建立了索引,會使用索引進行查詢(stage:IXSCAN)
db.users.find({age:26})

稀疏索引

索引的稀疏屬性可確保索引僅包含具有索引欄位的文件的條目。索引會跳過沒有索引欄位的文件。

將稀疏索引與唯一索引組合,以拒絕具有欄位重複值的文件,但忽略沒有索引鍵的文件。

-- 當document包含address欄位時才會建立索引:
db.userinfos.createIndex({address:1},{sparse:true})

TTL索引

TTL索引是MongoDB在指定時間後自動從集合中刪除文件的特殊索引。

這是某些型別的資訊的理想選擇,例如機器生成的事件資料,日誌和會話資訊,這些資訊只需要在資料庫中儲存有限的時間。

//新增測試資料
db.logs.insertMany([
  {_id:1,createtime:new Date(),msg:"log1"},
  {_id:2,createtime:new Date(),msg:"log2"},
  {_id:3,createtime:new Date(),msg:"log3"},
  {_id:4,createtime:new Date(),msg:"log4"}
]);
//在createtime欄位新增TTL索引,過期時間是120s(2分鐘)
db.logs.createIndex({createtime:1}, { expireAfterSeconds: 120 })

需要注意的是:TTL索引只能設定在date型別欄位(或者包含date型別的陣列)上,過期時間為欄位值+exprireAfterSeconds;document過期時不一定就會被立即刪除,因為mongoDB執行刪除任務的時間間隔是60s;

4 總結

本文簡單介紹了MongoDB的索引的基本概念和術語,為什麼MongoDB會採用B樹 而 MySQL會採用B+樹,常見的MongoDB索引的型別和應用,常見的索引屬性及應用。

下一篇,我們會學習MongoDB的事務管理相關知識,這是MongDB 4.x版本提供的新功能,在走向OLTP的路上邁出了堅實的一步。

本系列教程目錄:

MongoDB入門實戰教程(1)

MongoDB入門實戰教程(2)

MongoDB入門實戰教程(3)

MongoDB入門實戰教程(4)

MongoDB入門實戰教程(5)

MongoDB入門實戰教程(6)

MongoDB入門實戰教程(7)

MongoDB入門實戰教程(8)

參考資料

唐建法,《MongoDB高手課》(極客時間)

郭遠威,《MongoDB實戰指南》(圖書)

△推薦訂閱學習

作者:周旭龍

出處:https://edisonchou.cnblogs.com

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連結。