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