標籤:日誌 parse ddr 選擇性參數 sort stack 一個 views 之間
一、索引介紹
在mongodb中,索引用來支援高效查詢。如果沒有索引,mongodb必須在整個集合中掃描每個文檔來尋找匹配的文檔。但是如果建立合適的索引,mongodb就可以通過索引來限制檢查的文檔數量。
索引是一種特殊的資料結構,它儲存著集合中小部分的資料集,這種資料結構很容易遍曆。索引儲存著指定的欄位或欄位集合,這些欄位都是根據欄位值排序的。排序的索引條目能夠支援高效的等值匹配和基於範圍的查詢操作,此外,mongodb通過排序索引還能夠返回排好序的結果集。
從根本上來說,mongodb的索引類似於其他關係型資料庫的索引,它被定義在集合層面並支援任何欄位或子域,它採用B-tree資料結構。
二、索引概念
1、索引類型
MongoDB提供多種不同類型的索引。對於某一文檔或者內嵌文檔,你能夠在任意欄位或者內嵌欄位上建立索引。一般而言,你應該建立通用的面向使用者的索引。通過這些索引,確保mongodb掃描最少最有可能匹配的文檔。在mongodb的shell中,你能通過調用createIndex()方法建立一個索引。
1)單欄位索引
對於集合中的文檔,mongodb完全支援在任何欄位上建立索引。預設地,任何集合的_id欄位上都有一個索引,並且應用和使用者還可以添加額外的索引來支援重要的查詢和操作。mongodb既支援單欄位索引也支援多個欄位的複合索引,這裡先介紹單欄位索引,下面請看舉例說明:
> db.friends.insert({"name" :"Alice","age":27}) #集合friends中的一個文檔WriteResult({ "nInserted" : 1 })> db.friends.createIndex({"name" :1}) #在文檔的name欄位上建索引{ "createdCollectionAutomatically" : false, "numIndexesBefore" : 1, "numIndexesAfter" : 2, "ok" : 1}
db.collection.createIndex(keys,options)介紹:
| Parameter |
Type |
Description |
| keys |
document |
A document that contains the field and value pairs where the field is the index key and the value describes the type of index for that field. For an ascending index on a field, specify a value of 1; for descending index, specify a value of -1. MongoDB supports several different index types including text, geospatial, and hashed indexes. See Index Types for more information. |
| options |
document |
Optional. A document that contains a set of options that controls the creation of the index. See Optionsfor details. |
- _id欄位上索引:當一個集合被建立時,預設的會在_id欄位上建立一個升序唯一索引,這個索引是不能被刪除的。考慮到_id欄位是一個集合的主鍵,所以對於集合中每個文檔都應該有一個唯一的_id欄位,在該欄位中,你能儲存任意的唯一的值。_id欄位預設的值是ObjectId,它是在插入文檔時被自動產生。在分區集合環境中,如果你沒有指定_id欄位為shard key,那麼你的應用程式必須確保_id欄位值得唯一性,否則會報錯。常用的做法是:通過自動產生ObjectId標準值解決。
- 內嵌欄位索引:在內嵌文檔的任意欄位上,你也可以建立索引,就如同在文檔的一級欄位上建立一樣。不過,需要說明的是,在內嵌欄位上建立索引和在內嵌文檔上建立索引是有區別的,前者通過點號的方式訪問內嵌文檔中的欄位名。請看下面的例子:
> db.people.insert(... {... name:"John Doe",... address: {... street: "Main",... zipcode:"53511",... state: "WI"... }... }... )WriteResult({ "nInserted" : 1 }) > db.people.createIndex({"address.zipcode":1}) #通過address.zipcode方法引用zipcode欄位,注意需要加上雙引號。
{ "createdCollectionAutomatically" : false, "numIndexesBefore" : 1, "numIndexesAfter" : 2, "ok" : 1 }
> db.factories.insert( { metro: { city: "New York", state:"NY" }, name: "Giant Factory" })WriteResult({ "nInserted" : 1 })> db.factories.createIndex({metro:1}){ "createdCollectionAutomatically" : false, "numIndexesBefore" : 1, "numIndexesAfter" : 2, "ok" : 1}
上面的metro欄位是內嵌文檔,包含內嵌欄位city和state,所以建立方法同一級欄位建立方法一樣。
下面的查詢能夠用到該索引:
db.factories.find( { metro: { city: "New York", state: "NY" } } )
{ "_id" : ObjectId("56189565d8624fafa91cbbc1"), "metro" : { "city" : "New York", "state" : "NY" }, "name" : "Giant Factory" }
在內嵌文檔進行等值匹配查詢時,需要注意欄位的順序,例如下面的查詢將匹配不到任何文檔:
> db.factories.find( { metro: { state: "NY", city: "New York" } } )> >
2)複合索引
MongoDB支援複合式索引,所謂複合式索引,就是包含多個欄位的索引,一個複合式索引最多可以包含31個欄位。如果欄位屬於hash索引,這時複合式索引不能包括該欄位。
執行個體說明:
> db.products.insert( {"item": "Banana", "category": ["food","produce","grocery"], "location": "4th Street Store", "stock": 4, "type": "cases", "arrival": "2015"} )WriteResult({ "nInserted" : 1 })> db.products.createIndex({"item":1,"stock":1}) #建立複合索引,包含item和stock兩個欄位{ "createdCollectionAutomatically" : false, "numIndexesBefore" : 1, "numIndexesAfter" : 2, "ok" : 1}
這時下面兩個查詢是可以用到該複合索引的:
> db.products.find({"item":"Banana"})> db.products.find({"item":"Banana","stock":4})
【排序次序】
索引中的欄位可以按照升序(1)和降序(-1)排序,對於複合式索引,欄位的排序次序是很重要的,它直接影響排序操作能否用上索引。
下面舉例說明:events集合中文檔的欄位為username和date
db.events.find().sort( { username: 1, date: -1 } )
db.events.find().sort( { username: -1, date: 1 } )
db.events.find().sort( { username: 1, date: 1 } )
以上幾種查詢,由於排序次序不同,利用索引的情況也不一樣,現在建立如下索引:
db.events.createIndex( { "username" : 1, "date" : -1 } )
只有第一第二兩種情況能夠走該索引,第三種是無法利用該索引的。
【複合索引首碼】
所謂首碼是複合索引開始欄位的子集,如考慮下面的複合式索引:
{ "item": 1, "location": 1, "stock": 1 }
這時首碼可以有:
?{ item: 1 }?{ item: 1, location: 1 }
下面的查詢可以很好的利用該複合式索引:
?the item field, #只有一個item條件,以首碼開始?the item field and the location field, #兩個匹配條件,以首碼開始?the item field and the location field and the stock field. #全部匹配條件,包含首碼
而下面的查詢則無法利用該複合式索引:
?the location field,?the stock field, or?the location and stock fields.
對於一個集合中有複合式索引也有一個單欄位索引,如{a:1,b:1},{a:1},由於複合式索引首碼包括{a:1}索引,故第二個是冗餘的,可以刪除掉。
【總結】:mongodb中的複合式索引利用條件和其他關聯式資料庫的複合式索引基本相同。
3)多鍵索引(multikey index)
對於欄位是array類型的,mongodb將為array中每個元素建立索引,多鍵索引為數組欄位提供高效查詢。
db.collections.createIndex( { <field>: < 1 or -1 > } )
如果欄位是array類型的,在建立索引時,mongodb自動建立的是多鍵索引,無需我們顯式的指定索引類型。
索引掃描的邊界規定了在查詢期間利用索引搜尋的資料範圍,當一個索引上存在多個謂詞時,mongodb會試圖將這些謂詞通過交叉索引或複合式索引的方式進行合并,以產生比較小的範圍邊界。
對於一個複合索引,每個文檔中多鍵索引裡最多允許1個欄位是數群組類型,反過來,如果一個多鍵索引已存在,你將不能插入一個文檔,該文檔擁有兩個數群組類型的欄位。舉例說明:
{ _id: 1, a: [ 1, 2 ], b: [ 1, 2 ], category: "AB - both arrays" } #不能建立index { a: 1, b: 1 },因為這個索引中有兩個數群組類型的欄位
下面這種情況是允許建立index { a: 1, b: 1 }的:
{ _id: 1, a: [1, 2], b: 1, category: "A array" }{ _id: 2, a: 1, b: [1, 2], category: "B array" }
你不能指定一個多鍵索引作為分區key索引。雜湊索引也不能是多鍵。
你可以建立多鍵索引包含內嵌對象的情況:
{ _id: 1, item: "abc", stock: [ { size: "S", color: "red", quantity: 25 }, { size: "S", color: "blue", quantity: 10 }, { size: "M", color: "blue", quantity: 50 } ]}{ _id: 2, item: "def", stock: [ { size: "S", color: "blue", quantity: 20 }, { size: "M", color: "blue", quantity: 5 }, { size: "M", color: "black", quantity: 10 }, { size: "L", color: "red", quantity: 2 } ]}{ _id: 3, item: "ijk", stock: [ { size: "M", color: "blue", quantity: 15 }, { size: "L", color: "blue", quantity: 100 }, { size: "L", color: "red", quantity: 25 } ]}
然後建立一個多鍵索引:
db.inventory.createIndex( { "stock.size": 1, "stock.quantity": 1 } )
下面的查詢和排序都可以走這個索引的:
db.inventory.find( { "stock.size": "M" } )db.inventory.find( { "stock.size": "S", "stock.quantity": { $gt: 20 } } )db.inventory.find( ).sort( { "stock.size": 1, "stock.quantity": 1 } )db.inventory.find( { "stock.size": "M" } ).sort( { "stock.quantity": 1 } )
4)地理空間索引(Geospatial Indexes)
MongoDB專門提供一組索引和查詢機制來處理地理空間資訊,下面介紹mongodb中地理空間特性。
在儲存地理空間資訊資料之前,你需要決定使用哪種平面類型進行計算。你選擇的類型將影響你如何儲存資料、建立何種索引以及查詢的文法。MongoDB提供兩種平面類型:
曲面:為了計算球面幾何體,你需要儲存你的資料到曲面類型中並選擇2dsphere索引。將資料作為GeoJSON對象並按照座標軸順序儲存。
平面:為了計算歐幾裡得平面距離,將資料作為座標組儲存並採用2d索引。
為了建立一個基於GeoJSON資料格式的空間索引,使用db.collections.createIndex()方法來建立一個2dsphere索引,文法如下:
db.collection.createIndex( { <location field> : "2dsphere" } )
下面進行詳細示範:
首先,建立一個基於位置的集合places,該集合中儲存基於GeoJSON Point的位置資料文檔,如下:
db.places.insert( { loc : { type: "Point", coordinates: [ -73.97, 40.77 ] }, name: "Central Park", category : "Parks" })db.places.insert( { loc : { type: "Point", coordinates: [ -73.88, 40.78 ] }, name: "La Guardia Airport", category : "Airport" })
然後,建立一個基於loc欄位的2dsphere索引:
db.places.createIndex( { loc : "2dsphere" } )
當然,還可以建立包含2dsphere索引的複合索引:
db.places.createIndex( { loc : "2dsphere" , category : -1, name: 1 } )db.places.createIndex( { category : 1 , loc : "2dsphere" } ) #不像2d索引,這裡不要求2dsphere類型在第一個位置
【注意事項】:2dsphere索引的欄位必須是基於座標組和GeoJSON資料格式。
該索引類型用於資料作為點儲存在二維平面情景下,一般用在v2.2版本之前的基於座標組資料格式的,這裡不詳細介紹。
geoHaystack索引是一種特別的索引,一般被最佳化用來返回小地區的結果集。當用平面幾何體儲存資料形式時,用geoHaystack可以提高查詢效能。而對於使用曲面幾何體,2dsphere索引將是更好的選擇,它允許欄位重新排序,而geoHaystack要求第一個欄位必須是位置欄位。具體這裡不詳細介紹。
- 2d Index Internals:不常用,請查考官方文檔
5)散列索引
散列索引維護著索引欄位的散列值條目,散列函數能夠摺疊內建文檔並計算整個值的散列數,它不支援多鍵索引。
MongoDB的散列索引支援等值查詢,不支援基於範圍的查詢。你不能夠建立一個包含散列索引欄位的複合索引,也不能指定一個唯一索引在散列索引上,但是你可以在同一個欄位上建立散列索引和單欄位索引。
下面是建立散列索引的例子:
db.collection.createIndex( { _id: "hashed" } )
6)全文索引
MongoDB提供全文索引來支援文本字串的查詢效率,它可以建立在任何是字串類型或元素為字串數組的欄位上。一個集合最多可以有一個全文索引。
db.reviews.createIndex( { comments: "text" } )
當然你也可以建立一個包括多個欄位的全文索引,也就是複合索引可以包括全文索引:
db.reviews.createIndex( { subject: "text", comments: "text" } )
【指定權重】:權重是指全文索引欄位之間的比率,比如下面的content:10,keywords:5,表示content在查詢中出現2次,keywords才出現1次。
db.blog.createIndex( { content: "text", keywords: "text", about: "text" }, { weights: { content: 10, keywords: 5 }, name: "TextIndex" } )
【萬用字元】:建立全文索引時還可以利用萬用字元:
db.collection.createIndex( { "$**": "text" } )db.collection.createIndex( { a: 1, "$**": "text" } )
用這種方式建立的全文索引,只要是集合中存在字串類型的欄位全部加入到全文索引中,這種常常用在非結構化資料並且不確定欄位的情況下。
集合最多有一個全文索引如果查詢中包含$test運算式,不能用hint()函數不支援排序操作如果全文索引包含在複合索引中,那麼,這個複合索引不能包括多鍵和空間索引
2、索引屬性
MongoDB中除了支援上面索引類型外,還提供了一些常用的索引屬性。
1)TTL indexes
TTL索引是一類特殊的單欄位索引,用來自動的刪除集合中到期的資料。資料期限對於像機器碼產生、日誌及session等資料是很有用的。
db.eventlog.createIndex( { "lastModifiedDate": 1 }, { expireAfterSeconds: 3600 } ) #通過加上expireAfterSeconds定義TTL索引
從索引欄位值開始,過去了指定的秒數之後,TTL索引使文檔到期。而到期的閾值等於索引值加上指定的秒數。如果索引欄位是數群組類型的,那麼在TTL索引上會存在多個時間值,mongodb會採用最早的時間值進行閾值計算。如果索引欄位不是date類型的,那麼文檔永不到期;如果文檔不包含TTL索引,那麼文檔也永不到期。
mongodb會啟動後台TTL線程每隔60秒讀取索引的值並且刪除到期的資料,當TTL線程的狀態是active,你可以通過db.currentOp()查看到刪除操作。TTL索引並不保證當資料到期時立刻被刪除,可能會有一段時間的延遲。
1、TTL索引是單欄位索引,不支援複合式索引。2、_id欄位不支援TTL索引3、在capped集合中不支援TTL索引4、對已存在的TTL索引,你不能通過createIndex方法修改expireAfterSeconds的值,而是通過collMod命令連同索引集合標誌。要不然,只能刪掉重建。5、對於原來已存在的非TTL的單域索引,你不能再在該欄位上建TTL索引,為了把非TTL索引轉為TTL索引,這時你必須刪除原索引重建。
2)Unique indexes(唯一索引)
對於加了唯一索引的欄位,mongodb將會拒絕插入該欄位重複值的所有文檔。預設建立索引時,唯一索引參數時禁用的。
db.members.createIndex( { "user_id": 1 }, { unique: true } )
對於複合式索引,如果unique是true的話,那麼唯一性由這些欄位的組合值確定。
如果唯一索引欄位是數組或內建文件類型的,唯一索引並不保證裡面的值是否唯一,如下面例子:
db.collection.createIndex( { "a.b": 1 }, { unique: true } )db.collection.insert( { a: [ { b: 5 }, { b: 5 } ] } ) #這時完全可以插入
如果對於唯一索引欄位沒有值,那麼預設儲存null值。由於唯一性限制式,mongodb僅僅允許一次不包括該索引欄位的插入,如果大於1次,那麼就會報錯。舉例說明:
首先在x欄位上建立唯一索引:
db.collection.createIndex( { "x": 1 }, { unique: true } )
其次,執行不帶x欄位的插入語句:
db.collection.insert( { y: 1 } ) #這時可以插入,因為在插入之前集合中還不包括值為null的x
然後,再執行一次不包含x的插入語句:
db.collection.insert( { z: 1 } ) #這時報錯,由於之前插入了值為null的x
WriteResult({
"nInserted" : 0,
"writeError" : {
"code" : 11000,
"errmsg" : "E11000 duplicate key error collection: test.collection index: x_1 dup key: { : null }"
}
})
3)Partial Indexes(局部索引)
局部索引僅僅是為集合中某些滿足指定過濾條件的文檔建立的索引。通過對集合中部分文檔建立索引,故局部索引對儲存、索引建立和維護效能成本有較低的要求。
db.restaurants.createIndex( { cuisine: 1, name: 1 }, { partialFilterExpression: { rating: { $gt: 5 } } })
選擇性參數partialFilterExpression適用於所有的索引類型。
1 查詢謂詞必須包含過濾運算式2 查詢條件必須是局部索引結果集的本身或子集
對於上面的索引,下面舉例幾個查詢,通過利用條件,看是否能用上該局部索引:
1、db.restaurants.find( { cuisine: "Italian", rating: { $gte: 8 } } ) #可以走局部索引,因為查詢運算式的結果集是局部索引結果集的子集2、db.restaurants.find( { cuisine: "Italian" } ) #無法利用局部索引,因為不滿足條件1:查詢謂詞中沒有過濾運算式rating3、db.restaurants.find( { cuisine: "Italian", rating: { $lt: 8 } } ) #無法利用局部索引,因為走索引會導致不完整的結果集
對於帶唯一性限制式的局部索引,這個唯一性限制式只在滿足局部索引的範圍文檔中有效,而對於不走局部索引的,唯一性限制式不起作用。
{ "_id" : ObjectId("56424f1efa0358a27fa1f99a"), "username" : "david", "age" : 29 }{ "_id" : ObjectId("56424f37fa0358a27fa1f99b"), "username" : "amanda", "age" : 35 }{ "_id" : ObjectId("56424fe2fa0358a27fa1f99c"), "username" : "rajiv", "age" : 57 }db.users.createIndex( { username: 1 }, { unique: true, partialFilterExpression: { age: { $gte: 21 } } })#以下三個唯一性限制式可以有作用db.users.insert( { username: "david", age: 27 } )db.users.insert( { username: "amanda", age: 25 } )db.users.insert( { username: "rajiv", age: 32 } )#以下唯一性不起作用db.users.insert( { username: "david", age: 20 } )db.users.insert( { username: "amanda" } )db.users.insert( { username: "rajiv", age: null } )
4)Sparse Indexes(稀疏索引)
所謂稀疏索引就是僅包含有索引欄位的文檔條目,即使欄位的值為null。由於該索引會跳過沒有索引欄位的文檔,故取名“稀疏索引”。稀疏索引不包括所有集合中的文檔,相反的,非稀疏索引則包括集合中所有文檔。
在mongodb3.2及以後的版本中,推薦優先使用部分索引。
db.addresses.createIndex( { "xmpp_id": 1 }, { sparse: true } )
如果使用稀疏索引導致得到不完整的結果集,那麼mongodb將不用改索引除非顯式地用hint()函數指定使用。下面是使用例子:
{ "_id" : ObjectId("523b6e32fb408eea0eec2647"), "userid" : "newbie" }{ "_id" : ObjectId("523b6e61fb408eea0eec2648"), "userid" : "abby", "score" : 82 }{ "_id" : ObjectId("523b6e6ffb408eea0eec2649"), "userid" : "nina", "score" : 90 }db.scores.createIndex( { score: 1 } , { sparse: true } )db.scores.find( { score: { $lt: 90 } } ) #由於userid=newbie沒有score欄位,這樣就不滿足稀疏索引的條件,故只返回下面一個文檔
{ "_id" : ObjectId("523b6e61fb408eea0eec2648"), "userid" : "abby", "score" : 82 }
對於上面的集合,再來看個排序:
db.scores.find().sort( { score: -1 } ) #儘管該排序是在score索引欄位上,但是mongodb不會選擇稀疏索引,這樣就可以返回完整的結果集{ "_id" : ObjectId("523b6e6ffb408eea0eec2649"), "userid" : "nina", "score" : 90 }{ "_id" : ObjectId("523b6e61fb408eea0eec2648"), "userid" : "abby", "score" : 82 }{ "_id" : ObjectId("523b6e32fb408eea0eec2647"), "userid" : "newbie" }#為了指定使用稀疏索引,必須顯式用hint方法db.scores.find().sort( { score: -1 } ).hint( { score: 1 } ){ "_id" : ObjectId("523b6e6ffb408eea0eec2649"), "userid" : "nina", "score" : 90 }{ "_id" : ObjectId("523b6e61fb408eea0eec2648"), "userid" : "abby", "score" : 82 }
對於帶唯一約束的稀疏索引:
唯一性限制式只能在滿足稀疏索引的文檔上有作用,在其他文檔上都沒作用,如下:
{ "_id" : ObjectId("523b6e32fb408eea0eec2647"), "userid" : "newbie" }{ "_id" : ObjectId("523b6e61fb408eea0eec2648"), "userid" : "abby", "score" : 82 }{ "_id" : ObjectId("523b6e6ffb408eea0eec2649"), "userid" : "nina", "score" : 90 }db.scores.createIndex( { score: 1 } , { sparse: true, unique: true } )#下面四個可以進行插入db.scores.insert( { "userid": "AAAAAAA", "score": 43 } )db.scores.insert( { "userid": "BBBBBBB", "score": 34 } )db.scores.insert( { "userid": "CCCCCCC" } )db.scores.insert( { "userid": "DDDDDDD" } )#下面違反唯一性限制式db.scores.insert( { "userid": "AAAAAAA", "score": 82 } )db.scores.insert( { "userid": "BBBBBBB", "score": 90 } )
【四】MongoDB索引管理