索引類型
MongDB的索引分為以下幾種類型:單鍵索引、復合索引、多鍵索引、地理空間索引、全文本索引和哈希索引
單鍵索引(Single Field Indexes)
在一個鍵上創建的索引就是單鍵索引,單鍵索引是最常見的索引,如MongoDB默認創建的_id的索引就是單鍵索引。
例子:
{ "_id" : ObjectId(...), "name" : "Alice", "score" : 27 }
如果要在如上的文檔中創建單鍵索引,語句如下:
db.users.ensureIndex( { "score" : 1 } )
其存儲結構如下圖:
如果想要在子文檔的一個鍵上建立單鍵索引,其例子如下:
{ "_id": ObjectId(...), "name": "John Doe", "address": { "street": "Main", "zipcode": "53511", "state": "WI" } }
結構如上,其創建語句如下:
db.users.ensureIndex( { "address.zipcode": 1 } )
如果想要在整個子文檔上建立單鍵索引,其例子如下:
{ _id: ObjectId(...), metro: { city: "New York", state: "NY" }, name: "Giant Factory" }
結構如上,其創建語句如下:
db.factories.ensureIndex( { metro: 1 } )
下面的語句能夠使用其索引查找到上面的數據:
db.factories.find( { metro: { city: "New York", state: "NY" } } )
但再下面的這一條語句就查不到數據了,說明子文檔的查找必須是精確匹配,包括子文檔中的順序:
db.factories.find( { metro: { state: "NY", city: "New York" } } )
復合索引(Compound Indexes)
在多個鍵上建立的索引就是復合索引。
例子:
{ "_id": ObjectId(...), "userid": "aa1", "category": ["food", "produce", "grocery"], "location": "4th Street Store", "score": 4 }
如果要在如上的文檔中創建復合索引,語句如下:
db.products.ensureIndex( { "userid": -1, "score": 1 } )
userid是正序排列,score是逆序排列的。其存儲結構如下圖:
這個索引可以支持如下的排序:
db.products.find().sort( { userid: 1, score: -1 } ); db.products.find().sort( { userid: -1, score: 1 } ); db.products.find().sort( { userid: 1 } ); db.products.find().sort( { userid: -1 } );
不能支持如下的排序:
db.products.find().sort( { userid: 1, score: 1 } ); db.products.find().sort( { userid: -1, score: -1 } ); db.products.find().sort( { score: 1 } ); db.products.find().sort( { score: -1 } );
多鍵索引(Multikey Index)
如果在一個數組上面創建索引,MongoDB會自己決定,是否要把這個索引建成多鍵索引。
如果數據結構如下(兩種):
{a: [1, 2], b: 1}
{a: 1, b: [1, 2]}
你可以創建{ a: 1, b: 1 },會是多鍵復合索引。
多鍵索引結構如下:
例子:
{ "_id" : ObjectId("..."), "name" : "Warm Weather", "author" : "Steve", "tags" : [ "weather", "hot", "record", "april" ] }
文檔結構如上,如果你在tags上創建索引,就會創建出多鍵索引
如果文檔結構如下:
{ "_id": ObjectId(...), "title": "Grocery Quality", "comments": [{ author_id: ObjectId(...), date: Date(...), text: "Please expand the cheddar selection." }, { author_id: ObjectId(...), date: Date(...), text: "Please expand the mustard selection." }, { author_id: ObjectId(...), date: Date(...), text: "Please expand the olive selection." }] }
創建{ "comments.text": 1 } 索引也會是多鍵索引,且在如下查找語句中有效:
db.feedback.find( { "comments.text": "Please expand the olive selection." } )
地理空間索引(Geospatial Indexes and Queries)
MongoDB支持幾種類型的地理空間索引。其中最常用的是 2dsphere 索引(用於地球表面類型的地圖)和 2d 索引(用於平面地圖和時間連續的數據)。
1) 2dsphere
2dsphere 允許使用GeoJSON格式(http://www.geojson.org)指定點、線和多邊形。
點可以用形如[longitude, latitude]([經度, 緯度])的兩個元素的數組表示:
{ "name" : "New York City", "loc" : { "type" : "Point", "coordinates" : [50, 2] } }
線可以用一個由點組成的數組來表示:
{ "name" : "Hudson River", "loc" : { "type" : "LineString", "coordinates" : [[0, 1], [0, 2], [1, 2]] } }
多邊形是由線組成的數組來表示:
{ "name" : "New England", "loc" : { "type" : "Polygon", "coordinates" : [[[0, 1], [0, 2], [1, 2], [0, 1]]] } }
2dsphere 支持 Point、MultiPoint、LineString、MultiLineString、Polygon、MultiPolygon、Geometry Collection
loc 字段的名字可以是任意的,但是其中子對象是由 GeoJSON 指定的,不能改變。
在 ensureIndex 中使用 2dsphere 選項就可以創建一個地理空間索引:
db.world.ensureIndex({"loc": "2dsphere"})
可以使用多種不同的地理空間查詢:交集(intersection)、包含(within)以及接近(nearness)。查詢時,需要將希望查找的內容指定為形如 {"$geometry":geoJsonDesc} 的 GeoJSON 對象。
交集(intersection),使用 $geoIntersects 操作符:
var place = { "type" : "Polygon", "coordinates" : [[[0, 1], [0, 3], [50, 2], [0, 1]]] } db.world.find({"loc" : {"$geoIntersects" : {"$geometry" : place}}})
會查找出所有與 place 有交集的文檔。
包含(within),使用 $within 或者 $geoWithin 操作符:
db.world.find({"loc" : {"$within" : {"$geometry" : place}}})
接近(nearness),使用 $near 或者 $geoNear 操作符:
var place = { "type" : "Point", "coordinates" : [0, 3] } db.world.find({"loc" : {"$near" : {"$geometry" : place}}})
place 必須是個點,$near 是唯一一個會對查詢結果進行自動排序的地理空間操作符,$near 的返回結果是按照距離由近及遠排序的。
2) 2d
2d 索引用於扁平化表面,而不是球體表面,否則極點附近會出現大量的扭曲變形。
文檔中使用包含兩個元素的數組表示 2d 索引字段,不是 GeoJSON 的格式。
{ "name": "Water Temple", "tile": [32, 22] }
2d 索引只能對點進行索引。可以保存一個由點組成的數組,但是它只會被保存為由點組成的數組,不會被當成線。特別是對 $within 查詢來說,數組中的某個點在查詢范圍內,該文檔就會被找出。
在 ensureIndex 中使用 2d 選項就可以創建一個地理空間索引,也可以在其中設置最大最小邊界值和精度。默認情況下,最大值和最小值的范圍是[ -180 , 180 ),精度是26位的精度,大致相當於2英尺或60厘米的精度:
db.places.ensureIndex({"tile" : "2d"}, {"min" : -90, "max" : 90, "bits" : 20})
這會創建一個180*180大小的空間索引。
2d 索引的查詢比 2dsphere 簡單許多,可以直接使用$near 和 $within,而不必帶有 $geometry 子對象:
db.places.find({"tile": {"$near": [20, 21]}}).limit(10)
如果不加 limit,默認最多返回100條。
$within 可以查詢出某個形狀里的所有文檔,可以是矩形($box)、圓形($center)或者多邊形($polygon)。
db.places.find({"tile": {"$within": {"$box": [[0, 0], [30, 30]]}}})
$box 接收兩個元素,第一個元素是矩形的左下角坐標,第二個元素是矩形的右上角坐標。
db.places.find({"tile": {"$within": {"$center": [[30, 30], 10]}}})
$center 也接收兩個元素,第一個元素是圓心點的坐標,第二個是圓的半徑。
db.places.find({"tile": {"$within": {"$polygon": [[0, 0], [30, 30], [0, 25]]}}})
$polygon 接收多個點組成的數組,用來指定多邊形。
不管是 2dsphere 索引還是 2d 索引,都可以和其他字段一起組成復合索引:
db.world.ensureIndex({"name": 1, "loc": "2dsphere"})
全文索引(Text Indexes)
全文索引用於在文檔中搜索文本,我們也可以使用正則表達式來查詢字符串,但是當文本塊比較大的時候,正則表達式搜索會非常慢,而且無法處理語言理解的問題(如 entry 和 entries 應該算是匹配的)。使用全文索引可以非常快地進行文本搜索,就如同內置了多種語言分詞機制的支持一樣。創建索引的開銷都比較大,全文索引的開銷更大。創建索引時,需后台或離線創建。
{ "_id" : ObjectId("55a0e30427c9370e525032e9"), "content" : "This morning I had a cup of coffee.", "about" : "beverage", "keywords" : [ "coffee" ] } { "_id" : ObjectId("55a0e31027c9370e525032ea"), "content" : "Who doesn't like cake?", "about" : "food", "keywords" : [ "cake", "food", "dessert" ] }
文檔如上所示,在 content 上創建全文索引:
db.article.ensureIndex({"content": "text"})
使用全文索引查詢內容:
db.article.find({"$text": {"$search": "coffee"}})
如果要在所有字符串的鍵上進行文本搜索,請使用通配符 ($**) 來索引所有的包含字符串的鍵。創建了一個 article 中所有文檔的所有鍵的字符串進行索引的索引,且命名為 TextIndex:
db.article.ensureIndex({"$**": "text"}, {"name": "TextIndex"})
被索引的數據的默認語言決定了如何解析詞根以及忽略停止詞的規則。被索引數據的默認語言是英語。如果希望指定一個不同的語言,在創建全文索引時使用 default_language 選項。
支持如下語言(不支持中文,至少在 2.6 的版本中是這樣的):
- da or danish
- nl or dutch
- en or english
- fi or finnish
- fr or french
- de or german
- hu or hungarian
- it or italian
- nb or norwegian
- pt or portuguese
- ro or romanian
- ru or russian
- es or spanish
- sv or swedish
- tr or turkish
注:如果您將語言指定為值 "none" ,那么 text search 會使用簡單的分詞器,沒有停止詞也沒有取詞根處理。
db.quotes.ensureIndex({"content": "text"}, {"default_language": "spanish"})
哈希索引(Hashed Index)
哈希索引可以支持相等查詢,但是哈希索引不支持范圍查詢。您可能無法創建一個帶有哈希索引鍵的復合索引或者對哈希索引施加唯一性的限制。但是,您可以在同一個鍵上同時創建一個哈希索引和一個遞增/遞減(例如,非哈希)的索引,這樣MongoDB對於范圍查詢就會自動使用非哈希的索引。
db.active.ensureIndex({"a": "hashed"})
上面的操作將會在 active 的 a 鍵上創建一個哈希索引。
索引屬性
MongDB的索引屬性有以下幾種:TTL索引、唯一索引和稀疏索引。
TTL索引(TTL Indexes)
TTL索引是一種特殊索引,通過這種索引MongoDB會過一段時間后自動移除集合中的文檔。這對於某些類型的信息來說是一個很理想的特性,例如機器生成的事件數據、日志、會話信息等,這些數據都只需要在數據庫中保存有限時間。
TTL索引有如下限制:
-
它不支持復合索引 。
-
被索引鍵必須是日期類型的數據。
-
如果這個鍵存儲的是一個數組,且在索引中有多個日期類型的數據(和一篇文檔關聯),那么當其中最低 (比如,最早)過期閥值得到匹配時,這篇文檔就會過期失效了。
TTL索引不能保證過期數據會被立刻刪除。在文檔過期和MongoDB從數據庫中刪除文檔之間,可能會有延遲。刪除過期數據的后台任務 每隔60秒 運行一次。所以,在文檔過期 之后 和 后台任務運行或者結束 之前 ,文檔會依然存在於集合中。刪除操作的持續實際取決於您的 mongod 實例的負載。因此,在兩次后台任務運行的間隔間,過期數據可能會繼續留在數據庫中超過60秒。在其他方面,TTL索引是普通索引,並且如果可以的話,MongoDB會使用這些索引來匹配任意查詢。
db.token.ensureIndex({"lastUpdated": 1}, {"expireAfterSecs": 60*60*24})
token 超過24小時就會被刪除掉。
唯一索引(Unique Indexes)
唯一索引可以拒絕保存那些被索引鍵的值已經重復的文檔。
db.members.ensureIndex({"user_id": 1}, {unique: true})
默認情況下,MongoDB索引的 unique 屬性是 false 。如果對復合索引施加唯一性的限制,那么MongoDB就會強制要求復合值的唯一性,而不是分別對每個單獨的值要求唯一。
唯一性的限制是針對一個集合中不同文檔的。也即,唯一索引可以防止 不同 文檔的被索引鍵上存儲相同值,但是它不禁止同一篇文檔在被索引鍵存儲的數組里存儲的元素或者內嵌文檔是相同的值。在同一篇文檔存儲重復數據的情況下,重復的值只會被存入索引一次。
例如,一個集合有一個唯一索引 a.b :
db.collection.ensureIndex({"a.b": 1 }, {unique: true})
假如在集合中沒有其他的文檔的 a.b 鍵的值是 5 ,那么唯一索引將會允許將以下文檔插入集合:
db.collection.insert({a: [{b: 5}, {b: 5}]})
如果一篇文檔不包含唯一索引的被索引鍵,那么索引默認會為該文檔存儲一個null值。由於唯一性的限制,MongoDB將只允許有一篇可以不包含被索引鍵。如果超過一篇文檔不包含被索引鍵或沒有值,那么會拋出鍵重復(duplicate key)錯誤導致索引創建失敗。可以組合使用唯一性和稀疏索引的特性來過濾那些包含null值的文檔以避免這個錯誤。
稀疏索引(Sparse Indexes)
稀疏索引會跳過所有不包含被索引鍵的文檔。這個索引之所以稱為 “稀疏” 是因為它並不包括集合中的所有文檔。與之相反,非稀疏的索引會索引每一篇文檔,如果一篇文檔不含被索引鍵則為它存儲一個null值。
db.addresses.ensureIndex({"xmpp_id": 1}, {"sparse": true})
如果一個索引會導致查詢或者排序的結果集是不完整的,那么MongoDB將不會使用這個索引,除非用戶使用 hint() 方法來顯示指定索引。例如,查詢 { x: { $exists: false } } 將不會使用 x 鍵上的稀疏索引,除非顯示的hint。
2dsphere (version 2), 2d 和 text 這些索引總是稀疏的。
只要一篇文檔里有至少一個被索引鍵,稀疏且只包含有遞增/遞減索引鍵的復合索引就會索引這篇文檔。
至於稀疏且包含有地理索引鍵(例如 2dsphere, 2d)以及遞增/遞減索引鍵的復合索引,只有地理索引鍵的存在與否能決定一篇文檔是否被索引。
至於稀疏且包含了全文索引鍵和其他遞增/遞減索引鍵的復合索引,只有全文索引鍵的存在與否能決定是否索引該文檔。
一個稀疏且唯一的索引,可以防止集合中的文檔被索引鍵中出現重復值,同時也允許多個文檔里不包含被索引鍵。