MongoDB索引詳細介紹和使用(建議收藏)


純乾貨 | MongoDB索引詳細介紹和使用(建議收藏)


索引簡介

索引本質上是樹,最小的值在最左邊的葉子上,最大的值在最右邊的葉子上,使用索引可以提高查詢速度(而不用全表掃描),也可以預防髒數據的插入(如唯一索引)

索引即支持普通字段也支持內嵌文檔中某個鍵和數組元素進行索引

索引的原理:

對某個鍵按照升續或降續創建索引,查詢時首先根據查詢條件查找到對應 的索引條目找到,然後找對索引條目對應的文檔指針(文檔在磁盤上的存儲位置),根據文檔指針再去磁盤中找到相應的文檔,整個過程不需要掃描全表,速度比較快

["along"] ----> 0x0c965148(文檔指針) …… ["zhangsan"] ----> 0x0c965148(文檔指針)

索引的類型

  • 唯一索引 unique:保證數據的唯一不重複
  • 稀疏索引 sparse
  • TTL 索引 : 設置文檔的緩存時間,時間到了會自動刪除掉
  • 全文索引:便於大文本查詢(如概要、文章等長文本)
  • 複合索引:用於提高查詢速度
  • 二維平面索引:便於2d平面查詢
  • 地理空間索引:便於地理查詢

索引的管理

<code>// 創建索引
function ensureIndex(keys, options);
// 查詢索引
function getIndexes(filter);
// 刪除索引
function dropIndex("IndexName");
/<code>

一:唯一索引

<code>> db.foo.ensureIndex({"username": 1}, {"unique": true})
{
"createdCollectionAutomatically" : true,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
> db.foo.insert({"username": "mengday", "email": "[email protected]", "age": 26})
WriteResult({ "nInserted" : 1 })
// username 重複會報錯
> db.foo.insert({"username": "mengday", "email": "[email protected]"})
WriteResult({
"nInserted" : 0,
"writeError" : {
"code" : 11000,
"errmsg" : "E11000 duplicate key error collection: test.foo index: username_1 dup key: { : \"mengday\" }"
}
})


// 第一次 插入不包含索引鍵的文檔,插入成功,不包含索引鍵系統會默認為索引鍵的值為null
> db.foo.insert({"email": "[email protected]"})
WriteResult({ "nInserted" : 1 })

// 第二次插入不包含唯一索引的鍵,插入失敗,因為不包含鍵,鍵的值就null,第一次已經有一個值為null, 再插入null,就是重複
> db.foo.insert({"email": "[email protected]"})
WriteResult({
"nInserted" : 0,
"writeError" : {
"code" : 11000,
"errmsg" : "E11000 duplicate key error collection: test.foo index: username_1 dup key: { : null }"
}
})

// 對多個字段創建唯一索引(關係數據庫中的聯合主鍵)
db.user.ensureIndex({"username": 1, "nickname": 1}, {"unique": true})
>
/<code>

MongoDB是無結構型的NoSQL,同一個集合中的每條文檔可以包含某個鍵,也可以不包含,為了達到如果文檔中包含索引鍵,索引鍵的值必須唯一,如果不包含索引鍵那麼不用校驗唯一的效果,可以在創建索引時使用sparse: true, 也就是稀疏索引。

<code>> db.foo.drop()
true
> db.foo.ensureIndex({"username": 1}, {"unique": true, "sparse": true})
{
"createdCollectionAutomatically" : true,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1

}
> db.foo.insert({"email": "[email protected]"})
WriteResult({ "nInserted" : 1 })
> db.foo.insert({"email": "[email protected]"})
WriteResult({ "nInserted" : 1 })
> db.foo.insert({"username": "mengday3", "email": "[email protected]"})
WriteResult({ "nInserted" : 1 })
> db.foo.insert({"username": "mengday3", "email": "[email protected]"})
WriteResult({
"nInserted" : 0,
"writeError" : {
"code" : 11000,
"errmsg" : "E11000 duplicate key error collection: test.foo index: username_1 dup key: { : \"mengday3\" }"
}
})
/<code>

稀疏索引:對不存在的鍵就不進行索引,也就是該文檔上沒有建立索引,索引條目中也不包含 索引鍵為null的索引條目,所以再次插入不包含索引鍵的文檔不會報錯,直接插入。注意:稀疏索引不光和唯一索引配合使用,也可以單獨使用

對於唯一索引的看法:

唯一索引的目的是為了讓數據庫的某個字段的值唯一,為了確保數據的都是合法的,但是唯一索引在插入數據時會對數據進行檢查,一旦重複會拋出異常,效率會比較低,唯一索引只是保證數據庫數據唯一的最後一種手段,而不是最佳方式,更不是唯一方式,為了保證效率最好採用別的解決方案來保證數據的唯一合法性,儘量減少數據庫的壓力。


二: TTL索引

TTL索引是讓文檔的某個日期時間滿足條件的時候自動刪除文檔,這是一種特殊的索引,這種索引不是為了提高查詢速度的,TTL索引類似於緩存,緩存時間到了就過期了,就要被刪除了

<code>// expireAfterSeconds: 文檔生存的時間,單位是秒,索引鍵是日期類型的
// 如果當期時間大於索引鍵的時間加上緩存時間就會刪除該文檔
> db.foo.ensureIndex({"create_at": 1}, {"expireAfterSeconds": 60})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 2,
"numIndexesAfter" : 3,
"ok" : 1
}
/<code>

三:全文索引

<code>// 創建全文索引:字段:content, 鍵的類型:text(全文索引類型)
> db.blog.ensureIndex({"content": "text"})

> db.blog.insert({
"title": "MongoDB: The Definitive Guide",
"summary": "MongoDB Atlas Database as a Service",
"content": "The best way to deploy, operate, and scale MongoDB in the cloud. Available on AWS, Azure, and GoogleCloud Platform. Easily migrate your data to MongoDB Atlas with zero downtime."
})

// 使用全文索引進行查詢
> db.blog.find({"$text":{"$search": "best"}})
{ "_id" : ObjectId("5986c5c94fbaf781302810e2"), "title" : "MongoDB: The Definitive Guide", "summary" : "MongoDB Atlas Database as a Service", "content
" : "The best way to deploy, operate, and scale MongoDB in the cloud. Available on AWS, Azure, and GoogleCloud Platform. Easily migrate your data to M
ongoDB Atlas with zero downtime." }
>
/<code>

全文索引是用於對長文本檢索來使用的,是用正則表達式只能對字符串類型的值進行檢索。注意:創建索引是一件比較耗時耗費資源的事情,而全文索引更是耗時更厲害,如果對索引鍵的內容比較長,需要對內容進行分詞,會出現更嚴重的性能問題。

創建全文索引,建議在mongodb不忙的時候創建,mongodb的分詞現在好像不支持中文,如果是對內容比較小的比如小於100個漢字的可以試用一下mongodb的全文索引,如果是對一篇很長的文章使用全文索引這是非常不合適的,這會把mongodb累死的,對於內容比較多可以採取其他技術如Lucenne、Solr、ElasticSearch等技術


四:複合索引

創建索引時可以對一個字段創建索引,也可以對多個字段創建索引,對多個字段創建索引被稱為 複合索引或者組合索引

<code>> db.user.find()
{ "_id" : 1, "username" : "zhangsan", "age" : 25 }
{ "_id" : 2, "username" : "lisi", "age" : 18 }
{ "_id" : 3, "username" : "wangwu", "age" : 28 }
{ "_id" : 4, "username" : "fengwu", "age" : 27 }
>
// 1:為索引值以升續的方式創建索引條目,-1:代表降續
> db.user.ensureIndex({"username": 1})
{
"createdCollectionAutomatically" : false,

"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
// explain()函數用於查看當前查詢的一些信息,比如使用使用了索引等
> db.user.find({"username": "wangwu"}).explain()
/<code>
<code>// 創建組合索引(以後臺模式創建)
> db.user.ensureIndex({"username": 1, "age": 1}, {"background": true})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 2,
"numIndexesAfter" : 3,
"ok" : 1
}

> db.user.find({"username": "wangwu", "age": 28})

// 如果查詢時發現沒有使用到索引,可以使用hint函數強制使用索引查詢
> db.user.find().hint({"username": 1, "age": 1})

> db.user.update({"username": "zhangsan"}, {"$set": {"address": {"road": "yijiang", "code": 666}}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

// 對內嵌文檔中字段創建索引
> db.user.ensureIndex({"address.road": 1}, {"background": true})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 3,
"numIndexesAfter" : 4,
"ok" : 1
}
>
> db.user.find({"address.road": "yijiang"}).explain()

// 對數組創建索引,就是對數組中的每個元素分別創建索引,而不是對整個數組建立索引,對數組的每一個元素都創建索引,那麼維護索引的代價就比普通的值大

> db.user.update({"username": "zhangsan"}, {"$set": {"hobby": ["eat", "drink", "mm", "money"] }})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.user.ensureIndex({"hobby": 1}, {"background": true})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 4,
"numIndexesAfter" : 5,
"ok" : 1
}
> db.user.find({"hobby": "mm"}).explain()
/<code>

索引的選項:

  • name:"", 自定義索引的名稱,不配置系統會有默認的索引名
  • background: true, 默認是前臺模式, 創建索引是一件即費事又耗費資源的事情,創建索引是在前臺模式或者後臺模式下創建,在前臺模式下創建非常快,但是當有讀寫請求時會堵塞,在後臺模式下當有讀寫請求時並不堵塞,但是創建索引就會暫時暫停,後臺模式要比前臺模式慢的多
  • unique: true:,唯一索引
  • dropDups: true,是否強制刪除其他重複的文檔,默認不刪除,當索引鍵值重複時創建失敗
  • sparse: true, 稀疏索引: 只對包含了該索引鍵的文檔生成索引條目,不包含該鍵就跳過不生成索引鍵,可以和唯一索引配合使用,也可以單獨使用

注意:

  • 對於複合索引,相同的鍵,鍵在索引中的順序不同是屬於不同的索引,如:{"username": 1, "age": 1}和{"age": 1,"username": 1}是不同的索引
  • 對於複合索引,相同的鍵,每個鍵的排序不同也屬於不同的鍵,如 {"username": 1, "age": 1}和{"username": 1, "age": -1}是屬於不同的索引
  • 對於相同的鍵,鍵出現的順序相同,而每個鍵的排序都乘以 -11,是屬於相同的索引,如 {"username": 1, "age": -1}和{"username": -1, "age": -1}
  • 對於複合索引,存在隱式索引。隱式索引的意思是當對多個字段創建複合索引時,相當於也對所有字段組成的複合索引的前綴都創建了一個索引,例如 創建了複合索引:{"field1": 1, "field2": -1, "field3": -1, "field4": -1}, 也相當於同時創建了{"field1": 1}、{"field1": 1, "field2": -1}、{"field1": 1, "field2": -1, "field3": -1} 所有前綴組成的索引

對於索引的使用效率

  • 索引鍵基數越大,效率越高。基數:就是某個字段不同值的個數(相當於SQL中的 count(distinct key)),如性別就2個,如用戶名和郵箱幾乎都不同,所以不同值的個數就很多,基數越大,使用索引快速篩選掉不滿足條件的文檔越快,基數越小就不能快速篩選滿足條件的文檔
  • 一些特殊的操作符不能使用索引,如 $where、$exists
  • 一般取反的操作符索引利用率都比較低,如$not、$nin、$ne
  • 如果能使用$in操作符儘量不要使用$or操作符,因為or: 是執行兩次查詢操作,然後將結果合併起來,類似於union all,能使用in(單次查詢)就不要使用or操作符

什麼時候創建索引?

<code>當需要對查詢優化,或者經常使用某種查詢的時候可以創建索引來提高查詢效率
/<code>

應該選哪些字段創建索引?

<code>一般應該在基數比較高的鍵上建立索引,或者至少把基數較高的鍵放在複合索引的前面位置/<code>

本號致力於發表實用的技術文章,歡迎關注Java實用技術,及時閱讀每天的優質文章。


分享到:


相關文章: