DAY14 MongoDB 索引屬性與進階注意事項
昨天的文章介紹了各種索引以及建立方式,這篇會講一些使用上的一些經驗。
索引屬性 (Index properties)
在建立索引時還有一些屬性能設定,這些非常關鍵請務必先了解。
Unique
透過設定 index unique,使得該欄位變成唯一值(亦可用在多欄位)。使用方法是在後方加上{unique:true}
即可,例如:
1 | db.employee.createIndex({"department": 1}, {unique:true}) |
複合索引使用方式也是一樣。
Unique index 可以建立在不存在的欄位上,當一筆資料寫入時,會給予它 null
當作值; 下一筆進來後,若也沒有這個欄位,則會發生 key duplicated。
Partial
Partial 屬性是設定該欄位符合條件才會建立索引,這樣一來充分的減少 索引創建的成本與空間。
例如要針對 age > 30 以上的資料建立索引,語法如下:
1 | db.employee.createIndex({"department": 1}, {partialFilterExpression:{ age: { $gt:30 }}}) |
在以下的查詢中,會走 parptial index 的有哪些呢?
1 | 1. db.employee.find({"department": 1, "age": {$gt: 30}}}) |
Sparse
稀疏索引是針對存在的欄位做索引,上面那個是符合條件才建立,而這個是存在才建立,一樣省去建立與維護成本。這聽起來很像是 partial index 的一種,是的,沒錯。partial index 應用範圍更大,包含了 sparse 特性。
使用語法如下:
1 | db.employee.createIndex( { "assets": 1 }, { sparse: true } ) |
TTL
TTL index 顧名思義是針對文件建立 TTL,對於資料生命週期非常好用,基本上絕大部分 collection 需要建立,除非是不太會增加的 global configuration 就不用。
1 | db.employee.createIndex( { "expiredTime": 1 }, { expireAfterSeconds: 32 * 86400, name: "ttlIndex" }) |
Hidden
這應該是 4.4 版本蠻亮眼的功能,當同事們對於索引相持不同意見時,可以請 DBA 執行這個指令。此功能是隱藏某個索引,輸入後在執行計畫裡面都無法看到,但如果有資料更新,還是會幫新的文件建立索引喔,不用太擔心。
隱藏後可以觀察效能的各種改變,若確實沒有什麼 side effect,就可以真的刪除掉此索引了。大幅減少建立、刪除索引的時間、效能耗損。
1 | // hide |
索引注意事項1: 欄位順序
建立索引欄位的順序非常重要!!
假設我們建立了一把索引
1 | { "field_a": 1, "field_b": 1, "field_c": 1, "field_d":1} |
查詢條件如果是以下,都是ok的
- field_a
- field_a, field_b
- field_a, field_b, field_c
- field_a, field_b, field_c, field_d
但如果是以下
- field_b
- field_b, field_c
- field_c
- field_d
就很不恰當。儘管在 (execution) explain 可能看到走在 index 上,但效率也是極差,甚至你使用 hint 強迫走在索引上也沒用。依照我的使用經驗,建議查詢條件要符合前兩個欄位。
索引注意事項2: 排序欄位
我們很多時候會需要使用到排序功能,若要提升效能也必須把排序欄位加在索引內,這樣才能有效提升查詢效能。
例如索引 {field_a:1, field_b:1, field_c:1}
1 | db.collection.find( {field_1: 5} ).sort({ field_b:1 }) // ok |
索引注意事項3: 背景執行
建立索引時,可以在後面加上背景執行的設定,比較不會讓整個資料庫卡住。
1 | db.employee.createIndex({"department": 1}, {unique:true}, {background:true}) |
索引注意事項4: 覆蓋查詢
這是理想上最佳狀態,但實務上通常不太能滿足。當建立的索引欄位包含查詢結果欄位時,速度是最快的,因為不用實際去取得文件本身資料。
1 | db.employee.find({ index_field: {$lte:5000 } }, { _id:0 , index_field:1 }) |
上述範例就是查詢了有建立索引的欄位,且 project 這個欄位,這樣 MongoDB 實際上就不用走到文件本身去取資料,而是在索引上即可完成查詢。
索引注意事項5: 避免 intersection index
這個中文我也翻不上來,意思就是在一個查詢使用兩個以上索引。
- 所以這樣使用可不可以?可以
- MongoDB 會同時使用兩個索引嗎?會的
- 哪裡不好?效能
- 多不好?沒有比複合欄位好
所以什麼是 intersection index?
假設我們有兩把索引
1 | { field_a : 1} |
但我的查詢是…
1 | db.collection.find({ field_a: {$gte:3} , field_b: 100) |
這種情境下就是 intersection index,通常可以藉由調整索引或是文件結構來避免。
索引注意事項6: 避免整點 TTL
首先要有個觀念,什麼樣資料適合用在 TTL?
當然不會是交易類型的重要資料,要知道,即便是使用排程自動刪除或者TTL方式,都需要執行時間且要考慮到失敗的可能性,因此重要且有時效性的資料不會這樣做(心臟夠大例外)。
除此之外,通常整點會有一些固定的排程在執行,考量到 TTL 相當於刪除資料的概念,這個不是最緊急或重要的事會搶佔去系統資源,同時 oplog (MongoDB抄寫機制)會塞入大量資料,更不是一件好事,因此這點非常重要。
如果你能非常肯定資料量很小,且未來不會有大幅度的成長,那很多設計或操作倒是沒什麼影響。
小技巧
- 如何在已經有重複資料的 Collection 建立 Unique 索引?
加上 {dropDups:true}
即可!
1 | db.employee.createIndex({name:-1},{unique:true,dropDups:true}) |
- 我就是要在當這個欄位存在才設定unique,可以嗎?
可以,透過 sparse。語法如下:
1 | db.employee.createIndex({"department": 1}, {sparse: true, unique:true}) |
索引一直以來都是資料庫非常重要的一環,了解與善用非常重要,尤其盡量避免帶有RDBMS的觀念,雖然有些是相通的,但看到現在也能了解很多特點是 MongoDB 才有的,很多眉角在設計上無法光靠教科書或者教學文章就能透徹理解,畢竟每個情境都不同,最重要的還是實際去執行測試以及評估。那麼要如何評估索引的好壞呢~接著就是明天的事了 explain
!