本文來自獲得《2021MongoDB技術實踐與應用案例徵集活動》優秀案例獎作品
作者:雷徹
引文
MongoDB早期版本支持multi-key索引,加快數組檢索,很受程序員喜歡;在4.2版本又推出了wildCard索引,支持object和數組檢索。這兩種索引有相似之處,但在功能上wildCard更強大。日常工作中,有同學對這兩種索引的使用場景比較模糊,因此在這裏拋磚引玉,供大家借鑑。
Multi-key index
multi-key 支持對數組的高效查詢。
舉例:
db.employee1.insertMany([
{"name":"xiaoming","age":25,"ctime":new ISODate(),goodAt:
["mongodb","hbase","c++"]},{"name":"xiaohong","age":28,"ctime":new
ISODate(),goodAt:["es","java","c++"]},{"name":"xiaoguang","age":29,"ctime":new
ISODate(),goodAt:["mysql","c++","mongodb"]}
])
--index
db.employee1.createIndex({goodAt:1})
--查找
db.employee1.find({"goodAt":"mysql"})
explain的結果中,winningPlan.inputStage.stage爲IXSCAN ,走索引goodAt_1。這裏字
段"mysql"是一個完整的數組元素。下面再做兩個測試:
侵入查詢測試
如果數組元素爲json串,不能通過multi-key索引查詢某個元素的屬性
db.employee1.insertMany([{
"name":"a",
"age":25,
"ctime":new ISODate(),
"goodAt":[
{database:"mysql", lang:"c++"},
{database:"hbase",lang:"java"},
{database:"tidb",lang:"golang"}
]
}])
--截取json屬性,不支持;db.employee1.find({"goodAt":{"database":"mysql"}}).explain() /**走索引,結果爲
空,沒有滿足條件的元素**/
db.employee1.find({"goodAt":{"database":"mysql", "lang" : "c++" }}).explain()
/**走索引,結果不爲空**
建議使用如下寫法:
--遞歸
db.employee1.find({"goodAt.database":"mysql"}).explain() /**不走索引,結果不爲空
**/
如果要查詢database字段,只能對 goodAt.database 加索引
db.employee1.createIndex({"goodAt.database":1})
db.employee1.find({"goodAt.database":"mysql"}).explain() /**走索引,結果不爲空
**/
tips:
multi-key適用於對數組進行索引
不能對數組進行哈希
不支持對嵌套的對象進行查詢;
WildCard index
在上文中,查詢數組元素某個字段,就需要對字段單獨加索引,用起來很不方便。在MongoDB4.2版本引入了wildCard索引,支持對象,數組的檢索,並且可以侵入元素內部遍歷,非常方便。
多屬性集合,ok:{k1:v1,k2:v2},對ok建索引
舉例:
db.employee2.insertMany([
{
"name":"xiaoming",
"age":25,
"ctime":new ISODate(),
"goodAt":{
"database":["mongodb","hbase"],
"programLanguage":"c++"
}
},
{
"name":"xiaohong",
"age":28,
此時尚未建索引,查詢goodAt某個屬性,可以看到stage爲COLLSCAN
添加wildCard索引後
"ctime":new ISODate(),
"goodAt":{
"database":"mysql",
"programLanguage":"java",
"middleAware":"zookeeper"
}
},
{
"name":"xiaoguang",
"age":29,
"ctime":new ISODate(),
"goodAt":{
"database":"mongodb",
"programLanguage":"python",
"web":"nodejs"
}
}
])
此時尚未建索引,查詢goodAt某個屬性,可以看到stage爲COLLSCAN
db.employee2.find({"goodAt.database": "mysql"}).explain()
添加wildCard索引後
--對goodAt建索引
db.employee2.createIndex({ "goodAt.$**": 1 })
db.employee2.find({"goodAt.database": "mongodb"}).explain()
在元素"name":"xiaoming"中,goodAt.database字段的值爲數組,我們看看能否走索引匹配
db.employee2.find({"goodAt.database": "mongodb"}).explain()
wildCard索引也支持一個multi-key索引,可以對其中的數組元素進行索引匹配。
侵入查詢測試
進一步在wildCard索引中的數組元素下,添加對象,能否走索引?我們在goodAt.database屬性中,增加數組屬性,做屬下測試,目標是確認wildCard能否在數組中遞歸;
db.employees2.insert(
{
"name":"xiaohong1",
"age":29,
"ctime":new ISODate(),
"goodAt":{
"database":[{"rdb":"mysql"},
{"nosql":["mongodb","redis"]},
{"newsql":"tidb"}
],
"programLanguage":"go"
}
})
db.employee2.find({"goodAt.database.nosql": "mongodb"}).explain()
顯然,wildCard索引支持對數組元素中的檢索。
db.employees2.insert(
{
"name":"a",
"age":29,
"ctime":new ISODate(),
"goodAt":{
"database":{"rdb":"mysql","nosql":"mongodb","newsql":"tidb"},
"programLanguage":"go"
}
})
db.employee2.find({"goodAt.database.nosql": 1}).explain()
再回到我們multi-key中的例子,把索引改爲wildCard,是否可行?
db.employee1.dropIndexes('goodAt_1')
db.employee1.createIndex({ "goodAt.$**": 1 })
db.employee1.find({"goodAt.database":"mysql"}).explain()
可以滿足需求。注意:
wildCard不能支持兩層以上的數組嵌套
wildCard也不支持對如下查詢的索引訪問
db.employee1.find({"goodAt":{"database":"mysql"}}).explain()
查詢子屬性,建議使用 {"goodAt.database":1} 而不是 {goodAt:{"database":1}} ,對索引更友 好。
小結
multi-key和wildCard索引分別適用不同的場景,讓entry建模變得更加簡單。在使用時,需要注意:
multi-key索引主要加快數組遍歷,功能純粹;
wildCard可以侵入遍對象或數組內部,避免單屬性創建索引,更加靈活;
wildCard不會遍歷連續嵌套兩層以上的數組;
不建議太多層嵌套,儘量控制在3層以內;
關於作者:雷徹
搜狐集團數據庫團隊高級運維工程師,具有豐富的數據庫運維經驗,精通數據庫架構設計、性能優化及故障診斷,目前負責MySQL及MongoDB運維管理工作,並參與公司數據庫雲平臺開發建設,將運維經驗集成到公司數據庫雲平臺中。專注於CDC服務構建。願和大家多交流學習,爲社區貢獻一份力量!