一、索引簡介 再來老生常談一番,什麼是索引呢?資料庫索引與書籍的索引類似。有了索引就不需要翻整本書,資料庫可以直接在索引中查找,在索引中找到條目以後,就可以直接跳轉到目標文檔的位置,這能使查找速度提高幾個數量級。 然而,使用索引是有代價的:對於添加的每一個索引,每次寫操作(插入、更新、刪除)都將耗費 ...
一、索引簡介
再來老生常談一番,什麼是索引呢?資料庫索引與書籍的索引類似。有了索引就不需要翻整本書,資料庫可以直接在索引中查找,在索引中找到條目以後,就可以直接跳轉到目標文檔的位置,這能使查找速度提高幾個數量級。
然而,使用索引是有代價的:對於添加的每一個索引,每次寫操作(插入、更新、刪除)都將耗費更多的時間。這是因為,當數據發生變動時,MongoDB不僅要更新文檔,還要更新集合上的所有索引。因此,MongoDB限制每個集合上最多只能有64個索引。通常,在一個特定的集合上,不應該擁有兩個以上的索引。於是,挑選合適的欄位建立索引非常重要。
- 索引基數
基數(cardinality)就是集合中某個欄位擁有不同值的數量。比如 gender 欄位,基數一般就男女 2個而已;而像 mobile 這樣的欄位,基數就會特別大。
通常來講,一個欄位的基數越高,這個欄位上的索引就越有用。這是因為索引能夠迅速將搜索範圍縮小到一個比較小的結果集。對於低基數的欄位,索引通常無法排除掉大量可能的匹配。假設我們在"gender"上有一個索引,需要查找名為Susan的女性用戶。通過這個索引,只能將搜索空間縮小到大約50%。
tips:在關係型資料庫中類似 gender 這樣的欄位可以使用點陣圖索引。
- 索引原理淺析
我們以一個索引 {"age" : 1, "username" : 1} 來看看索引在MongoDB 中是如何存儲的,大致是這個樣子:
每一個索引條目都包含一個"age"欄位 和 "username"欄位,並且指向文檔在磁碟中的存儲位置。註意,這裡的 age 嚴格的按照升序排序,並且相同的 age 對應的 username 也嚴格的按照升序排序。
來看個例子 :db.users.find({"age" : 21}).sort({"username" : -1})
這個索引對於這個查詢來說是非常高效的,因為它可以馬上定位到 age = 21 的位置,並且age = 21 中的 username 已經是排序好的。
tips:排序方向並不重要:MongoDB可以在任意方向上對索引進行遍歷。
tips:查詢中的欄位順序無關緊要,MongoDB 會自動找出可以使用索引的欄位,而無視查詢的欄位順序。
- $操作符如何使用索引
有一些查詢完全無法使用索引,也有一些查詢能夠比其他查詢更高效地使用索引。
$where:無法使用索引。
$nin:無法使用索引。
$exists:無法使用索引。因為在索引中,不存在的欄位和null欄位的存儲方式是一樣的,查詢必須遍歷每一個文檔檢查這個值是否真的為null還是根本不存在。
$ne:可以使用索引,但並不是很高效。因為必須遍歷整個索引條目才能找到結果的文檔。
$not:能夠使用索引,但通常不知道如何使用索引,從而退化成全表掃描。
$or:能夠使用索引,但是$or 查詢會將 or 的條件拆分成多個獨立的查詢,然後再將結果合併在一起。這是很低效的,不建議用。建議用 $in 取代 $or 。
設計多鍵索引的時候要記得,要把基數大的欄位放在索引的前面,因為這樣能更快縮小查詢的範圍。
二、索引類型
- 複合(組合)索引
複合索引就是一個建立在多個欄位上的索引。
如果查詢中有多個排序方向或者查詢條件中有多個鍵,複合索引就非常有效。
db.userInfo.ensureIndex({"age":1,"age":1})
進行多鍵排序時,索引的方向尤為重要。儘量做到多鍵排序的方向和複合索引的方向是一致的,因為這能很大的避免在記憶體中進行排序的運算。
tips:相互反轉(在每個方向上都乘以-1)的索引是等價的:{"age" : 1, "user name" : -1}適用的查詢與{"age" : -1, "username" : 1}是完全一樣的。
複合索引具有雙重功能,而且對不同的查詢可以表現為不同的索引。如果有一個{"age" :1, "username" : 1}索引,"age"欄位會被自動排序,就好像有一個{"age" : 1}索引一樣。因此,這個複合索引可以當作{"age" : 1}索引一樣使用。
- 唯一索引
唯一索引可以確保集合的每一個文檔的指定鍵都有唯一值。我們熟悉的 "_id" 索引就是一個唯一索引(但它不能被刪除,而其他唯一索引是可以刪除的)。
db.users.ensureIndex({"username" : 1}, {"unique" : true})
定義了唯一索引後,這個鍵就不允許插入重覆的值了,否則會拋異常。
tips:A 欄位不存在 和 A 欄位為 null 是互斥的!
在已有的集合上創建唯一索引可能會報錯,因為集合中可能已經有重覆的值了。在極少數情況下,可能希望直接刪除重覆的值。創建索引時使用"dropDups"選項,如果遇到重覆的值,第一個會被保留,之後的重覆文檔都會被刪除。
db.users.ensureIndex({"username" : 1}, {"unique" : true, "dropDups" : true})
- 稀疏索引
在有些情況下,你可能希望唯一索引只對包含相應鍵的文檔生效。如果有一個可能存在也可能不存在的欄位,但是當它存在時,它必須是唯一的,這時就可以將unique和sparse選項組合在一起使用,創建唯一稀疏索引。註意:MongoDB中的稀疏索引(sparse index)與關係型資料庫中的稀疏索引是完全不同的概念。基本上來說,MongoDB中的稀疏索引只是不需要將每個文檔都作為索引條目。並且,稀疏索引並不一定是唯一的。
db.ensureIndex({"email" : 1}, {"unique" : true, "sparse" : true})
當某個查詢使用了稀疏索引,就不會返回不包含這個欄位的文檔。因為稀疏索引並沒有把每個文檔都作為索引條目。
- 覆蓋索引
如果你的查詢只需要查找索引中包含的欄位,那就根本沒必要獲取實際的文檔。當一個索引包含用戶請求的所有欄位,可以認為這個索引覆蓋了本次查詢。所以,儘可能使用投射篩選返回的欄位,比如 {"_id":0,"age":1} 等,來實現覆蓋索引。
三、索引管理
- 新建索引
普通索引
db.userInfo.ensureIndex({"name":1},{"name","MyIndex"})
"1" 表示按照name進行升序,"-1" 表示按照name進行降序。
預設的索引以 key1_1_key2_-1 這樣的方式命名,可以手動指定索引的名字,如上。
對象索引
可以對整個對象建立索引,或者對對象的某個元素使用索引。
db.users.ensureIndex({"loc" : 1})
只有在進行與對象欄位順序完全匹配的子文檔查詢時(比如db.users.find({"loc" :{"ip" : "123.456.789.000", "city" : "Shelbyville", "state" :"NY"}}})),查詢優化器才會使用"loc"上的索引。
db.users.ensureIndex({"loc.city" : 1})
有涉及到對象city的查詢都會使用這個索引。
數組索引
對數組建立索引,實際上是對數組的每個元素建立一個索引條目。比如一個文檔中的數組欄位有20個元素,那麼該文檔就擁有了20個索引條目!所以對數組欄位的索引建立要慎重。
- 刪除索引
db.userInfo.dropIndexes("name_1")
刪除指定索引
db.userInfo.dropIndexes()
刪除除了_id 以外的所有索引
- 操作索引
獲取當前索引列表:db.userInfo.getIndexes()
hint 暴力選擇某種索引:db.userInfo.find({name:'zhangsan',birthday:'1989-3-2'}).hint({"name":1,"birthday":1})
強制使用全表掃描:db.userInfo.find({"birthday" : {"$lt" :"1989-3-2"}}).hint({"$natural" : 1})
索引分析函數explain:MongoDB 3.0前 和 MongoDB 3.0後存在很大的差異,這裡只簡單說明下,如果想詳細瞭解的話,可以關註該作者的文章:
MongoDB 3.0 前:db.driverLocation.find({"areaCode":"350203"}).explain()
cursor:表掃描方式 (basicCursor:順序查找)
nscanned:瀏覽了多少文檔
n:最終返回了幾個文檔
millis:總共耗時了多少毫秒
scanAndOrder:是否必須在記憶體中對數據進行排序
MongoDB 3.0 後:db.driverLocation.find({"areaCode":"350203"}).explain("executionStats")
executionTimeMillis:該query的整體查詢時間
nReturned:查詢返回的條目
totalKeysExamined:索引掃描條目
totalDocsExamined:文檔掃描條目