mongodb索引
標簽: MongoDB
索引基礎知識
什么是索引
索引最常用的比喻就是書籍的目錄,查詢索引就像查詢一本書的目錄。本質上目錄是將書中一小部分內容信息(比如題目)和內容的位置信息(頁碼)共同構成,而由于信息量小(只有題目),所以我們可以很快找到我們想要的信息片段,再根據頁碼找到相應的內容。同樣索引也是只保留某個域的一部分信息(建立了索引的field的信息),以及對應的文檔的位置信息。
假設我們有如下文檔(每行的數據在MongoDB中是存在于一個Document當中)
姓名 | id | 部門 | city | score |
---|---|---|---|---|
張三 | 2 | xxx | Beijing | 90 |
李四 | 1 | xxx | Shanghai | 70 |
王五 | 3 | xxx | guangzhou | 60 |
假如我們想找id為2的document(即張三的記錄),如果沒有索引,我們就需要掃描整個數據表,然后找出所有為2的document。當數據表中有大量documents的時候,這個時間就會非常長(從磁盤上查找數據還涉及大量的IO操作)。建立索引后會有什么變化呢?MongoDB會將id數據拿出來建立索引數據,如下
索引值 | 位置 |
---|---|
1 | pos2 |
2 | pos1 |
3 | pos3 |
這樣我們就可以通過掃描這個小表找到document對應的位置。
查找過程示意圖如下:
圖片來源MongoDB官網
為什么這樣速度會快呢?這主要有幾方面的因素
- 索引數據通過B+樹來存儲,從而使得搜索的時間復雜度為O(logdN)級別的(d是B+樹的度, 通常d的值比較大,比如大于100),比原先O(N)的復雜度大幅下降。這個差距是驚人的,以一個實際例子來看,假設d=100,N=1億,那么O(logdN) = 8, 而O(N)是1億。是的,這就是算法的威力。
- 索引本身是在高速緩存當中,相比磁盤IO操作會有大幅的性能提升。(需要注意的是,有的時候數據量非常大的時候,索引數據也會非常大,當大到超出內存容量的時候,會導致部分索引數據存儲在磁盤上,這會導致磁盤IO的開銷大幅增加,從而影響性能,所以務必要保證有足夠的內存能容下所有的索引數據)
當然,事物總有其兩面性,在提升查詢速度的同時,由于要建立索引,所以寫入操作時就需要額外的添加索引的操作,這必然會影響寫入的性能,所以當有大量寫操作而讀操作比較少的時候,且對讀操作性能不需要考慮的時候,就不適合建立索引。當然,目前大多數互聯網應用都是讀操作遠大于寫操作,因此建立索引很多時候是非常劃算和必要的操作。
關于索引原理的詳細解釋可以參考文章MySQL索引背后的數據結構及算法原理,雖然講得是MySQL但是原理相似。
MongoDB有哪些類型的索引
單字段索引 (Single Field Index)
這個是最簡單最常用的索引類型,比如我們上邊的例子,為id建立一個單獨的索引就是此種類型。
# 為id field建立索引,1表示升序,-1表示降序,沒有差別
db.employee.createIndex({'id': 1})
需要注意的是通常MongoDB會自動為我們的文檔插入'_id' field,且已經按照升序進行索引,如果我們插入的文檔中包含有'_id' field,則MongoDB就不會自動創建'_id' field,但是需要我們自己來保證唯一性從而唯一標識一個文檔
復合索引 (Compound Index)
符合索引的原理如下圖所示:
復合索引示意圖
上圖查詢索引的時候會先查詢userid,再查詢score,然后就可以找到對應的文檔。
對于復合索引需要注意以下幾點:
索引field的先后順序很關鍵,影響有兩方面:
- MongoDB在復合索引中是根據prefix排序查詢,就是說排在前面的可以單獨使用。我們創建一個如下的索引
db.collection.createIndex({'id': 1, 'city': 1, 'score': 1})
我們如下的查詢可以利用索引
db.collection.find({'id': xxx})
db.collection.find({'id': xxx, 'city': xxx})
db.collection.find({'id': xxx, 'city':xxx, 'score': xxxx})
但是如下的查詢無法利用該索引
db.collection.find({'city': xxx})
db.collection.find({'city':xxx, 'score': xxxx})
還有一種特殊的情況,就是如下查詢:
db.collection.find({'id': xxx, 'score': xxxx})
這個查詢也可以利用索引的前綴'id'來查詢,但是卻不能針對score進行查詢,你可以說是部分利用了索引,因此其效率可能不如如下索引:
db.collection.createIndex({'id': 1, 'score': 1})
2.過濾出的document越少的field越應該放在前面,比如此例中id如果是唯一的,那么就應該放在最前面,因為這樣通過id就可以鎖定唯一一個文檔。而如果通過city或者score過濾完成后還是會有大量文檔,這就會影響最終的性能。
索引的排序順序不同
復合索引最末尾的field,其排序順序不同對于MongoDB的查詢排序操作是有影響的。
比如:
db.events.createIndex( { username: 1, date: -1 } )
這種情況下, 如下的query可以利用索引:
db.events.find().sort( { username: 1, date: -1 } )
但是如下query則無法利用index進行排序
db.events.find().sort( { username: 1, date: 1 } )
多key索引 (Multikey Index)
這個主要是針對數據類型為數組的類型,如下示例:
{"name" : "jack", "age" : 19, habbit: ["football, runnning"]}
db.person.createIndex( {habbit: 1} ) // 自動創建多key索引
db.person.find( {habbit: "football"} )
其它類型索引
另外,MongoDB中還有其它如哈希索引,地理位置索引以及文本索引,主要用于一些特定場景,具體可以參考官網,在此不再詳解
索引屬性
索引主要有以下幾個屬性:
- unique:這個非常常用,用于限制索引的field是否具有唯一性屬性,即保證該field的值唯一
- partial:很有用,在索引的時候只針對符合特定條件的文檔來建立索引,如下
db.restaurants.createIndex(
{ cuisine: 1, name: 1 },
{ partialFilterExpression: { rating: { $gt: 5 } } } //只有當rating大于5時才會建立索引
)
這樣做的好處是,我們可以只為部分數據建立索引,從而可以減少索引數據的量,除節省空間外,其檢索性能也會因為較少的數據量而得到提升。
- sparse:可以認為是partial索引的一種特殊情況,由于MongoDB3.2之后已經支持partial屬性,所以建議直接使用partial屬性。
- TTL。 可以用于設定文檔有效期,有效期到自動刪除對應的文檔。
通過explain結果來分析性能
我們往往會通過打點數據來分析業務的性能瓶頸,這時,我們會發現很多瓶頸都是出現在數據庫相關的操作上,這時由于數據庫的查詢和存取都涉及大量的IO操作,而且有時由于使用不當,會導致IO操作的大幅度增長,從而導致了產生性能問題。而MongoDB提供了一個explain工具來用于分析數據庫的操作。直接拿官網的示例來做說明:
假設我們在inventory collection中有如下文檔:
{ "_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 }
假設此時沒有建立索引,做如下查詢:
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來分析整個查詢的過程:
# explain 有三種模式: "queryPlanner", "executionStats", and "allPlansExecution".
# 其中最常用的就是第二種"executionStats",它會返回具體執行的時候的統計數據
db.inventory.find(
{ quantity: { $gte: 100, $lte: 200 } }
).explain("executionStats")
explain的結果如下:
{
"queryPlanner" : {
"plannerVersion" : 1,
...
"winningPlan" : {
"stage" : "COLLSCAN",
...
}
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 3, # 查詢返回的document數量
"executionTimeMillis" : 0, # 執行查詢所用的時間
"totalKeysExamined" : 0, # 總共查詢了多少個key,由于沒有使用索引,因此這里為0
"totalDocsExamined" : 10, # 總共在磁盤查詢了多少個document,由于是全表掃描,我們總共有10個documents,因此,這里為10
"executionStages" : {
"stage" : "COLLSCAN", # 注意這里,"COLLSCAN"意味著全表掃描
...
},
...
},
...
}
上面的結果中有一個"stage"字段,上例中stage為"COLLSCAN",而MongoDB總共有如下幾種stage:
- COLLSCAN – Collection scan
- IXSCAN – Scan of data in index keys
- FETCH – Retrieving documents
- SHARD_MERGE – Merging results from shards
- SORT – Explicit sort rather than using index order
現在我們來創建一個索引:
db.inventory.createIndex( { quantity: 1 } )
再來看下explain的結果
db.inventory.find(
{ quantity: { $gte: 100, $lte: 200 } }
).explain("executionStats")
結果如下:
{
"queryPlanner" : {
"plannerVersion" : 1,
...
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN", # 這里"IXSCAN"意味著索引掃描
"keyPattern" : {
"quantity" : 1
},
...
}
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 3,
"executionTimeMillis" : 0,
"totalKeysExamined" : 3, # 這里nReturned、totalKeysExamined和totalDocsExamined相等說明索引沒有問題,因為我們通過索引快速查找到了三個文檔,且從磁盤上也是去取這三個文檔,并返回三個文檔。
"totalDocsExamined" : 3,
"executionStages" : {
...
},
...
},
...
}
再來看下如何通過explain來比較compound index的性能,之前我們在介紹復合索引的時候已經說過field的順序會影響查詢的效率。有時這種順序并不太好確定(比如field的值都不是unique的),那么怎么判斷哪種順序的復合索引的效率高呢,這就像需要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 }
現在我們要比較如下兩種復合索引
db.inventory.createIndex( { quantity: 1, type: 1 } )
db.inventory.createIndex( { type: 1, quantity: 1 } )
分析索引 { quantity: 1, type: 1 }的情況
# 結合hint和explain來進行分析
db.inventory.find(
{ quantity: { $gte: 100, $lte: 300 }, type: "food" }
).hint({ quantity: 1, type: 1 }).explain("executionStats") # 這里使用hint會強制數據庫使用索引 { quantity: 1, type: 1 }
explain結果
{
"queryPlanner" : {
...
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"quantity" : 1,
"type" : 1
},
...
}
}
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 2,
"executionTimeMillis" : 0,
"totalKeysExamined" : 5, # 這里是5與totalDocsExamined、nReturned都不相等
"totalDocsExamined" : 2,
"executionStages" : {
...
}
},
...
}
再來看下索引 { type: 1, quantity: 1 } 的分析
db.inventory.find(
{ quantity: { $gte: 100, $lte: 300 }, type: "food" }
).hint({ type: 1, quantity: 1 }).explain("executionStats")
結果如下:
{
"queryPlanner" : {
...
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"type" : 1,
"quantity" : 1
},
...
}
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 2,
"executionTimeMillis" : 0,
"totalKeysExamined" : 2, # 這里是2,與totalDocsExamined、nReturned相同
"totalDocsExamined" : 2,
"executionStages" : {
...
}
},
...
}
可以看出后一種索引的totalKeysExamined返回是2,相比前一種索引的5,顯然更有效率。
作者:geekpy
鏈接:https://www.jianshu.com/p/2b09821a365d
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
db.getCollection('driverMongo').getIndexes()
db.getCollection('driverMongo').find({driverId:2140,groupId:34,name:'123456'}).explain('executionStats')
智能推薦
MongoDB備份、索引、復制
1. 數據備份與恢復 2. 索引 索引通常能夠極大的提高查詢的效率,如果沒有索引MongoDB在讀取數據時必須掃描集合中的每個文件并選取那些符合查詢條件的記錄 這種掃描全集合的查詢效率是非常的,特別在處理大量的數據時,查詢可以要花費幾十秒甚至幾分鐘,這對網站的性能是非常致命的 ensureIndex() 接收可選參數,可選參數列表如下: 3. MongoDB復制 3.1 主從復制 3.2 副本集 ...
mongodb--索引(筆記)
介紹 Index 合適的索引可以大大提高數據庫搜索能力 對文檔的部分內容進行排序的數據結構 加快文檔查詢和文檔排序的速度 類型:單鍵索引、復合鍵索引、多鍵索引 特性:唯一性、稀疏性、生存時間 創建索引 db.collections.createIndex() 數組中的每一個元素都會在多鍵索引中創建一個鍵 創建唯一性索引 稀疏性:復合鍵索引也可以具有稀疏性。如果文檔中沒有完全...
MongoDB復合索引詳解
摘要: 對于MongoDB的多鍵查詢,創建復合索引可以有效提高性能。 ### 什么是復合索引? 復合索引,即**Compound Index**,指的是將多個鍵組合到一起創建索引,這樣可以加速匹配多個鍵的查詢。不妨通過一個簡單的示例理解復合索引。 students集合如下: 在name和age兩個鍵分別創建了索引(_id自帶索引): 當進行多鍵查詢時,可以通過[explian()](https:/...
mongoDB-特殊索引
全文檢索 什么是? 使用正則表達式模糊檢索文本內容,對于大段文本來說,效率很低,而且無法理解語義 這個時候可以使用全文檢索,可以快速進行文本檢索,且內置多種語言分詞機制,可理解語義, MongoDB提供文本索引來支持全文檢索,文本索引可以建立在任何字符串格式的鍵上,甚至可 以建立在以字符為元素的數組上 問題: &nbs...
猜你喜歡
freemarker + ItextRender 根據模板生成PDF文件
1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...
電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!
Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...
requests實現全自動PPT模板
http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...
Linux C系統編程-線程互斥鎖(四)
互斥鎖 互斥鎖也是屬于線程之間處理同步互斥方式,有上鎖/解鎖兩種狀態。 互斥鎖函數接口 1)初始化互斥鎖 pthread_mutex_init() man 3 pthread_mutex_init (找不到的情況下首先 sudo apt-get install glibc-doc sudo apt-get install manpages-posix-dev) 動態初始化 int pthread_...