摘要
數據庫性能對軟件整體性能有着至關重要的影響,對於Mongodb數據庫常用的性能優化方法主要有:
- 范式化與反范式化;
- 填充因子的使用;
- 索引的使用;
一. 范式化與反范式化
范式是為了消除重復數據減少冗余數據,從而讓數據庫內的數據更好的組織,讓磁盤空間得到更有效利用的一種標准化標准,滿足高等級的范式的先決條件是滿足低等級范式。在數據庫設計階段,明確集合的用途是對mongodb數據庫性能調優非常重要的一步。根據集合中數據最常用的操作,對於頻繁更新和頻繁查詢的集合,我們最需要關注的重點是他們的范式化程度。
1.1 范式化
1.1.1 范式化的優點:
- 范式化的數據庫更新起來更加快;
- 范式化之后,只有很少的重復數據,只需要修改更少的數據;
- 范式化的表更小,可以在內存中執行;
- 很少的冗余數據,在查詢的時候需要更少的distinct或者group by語句。
1.1.2 范式化的缺點:
- 范式化的表,在查詢的時候經常需要很多的關聯,因為單獨一個表內不存在冗余和重復數據。這導致,稍微復雜一些的查詢語句在查詢范式的schema上都可能需要較多次的關聯。這會增加讓查詢的代價,也可能使一些索引策略無效。因為范式化將列存放在不同的表中,而這些列在一個表中本可以屬於同一個索引。
1.1.3 范式化設計的例子:
以存儲一篇圖書及其作者為例,作者的信息包括作者的姓名,年齡,國籍。使用范式化的設計如下:
{
"_id" : ObjectId("5124b5d86041c7dca81917"),
"title" : "如何使用MongoDB",
"author" : [
ObjectId("144b5d83041c7dca84416"),
ObjectId("144b5d83041c7dca84418"),
ObjectId("144b5d83041c7dca84420"),
]
}
將作者(comment) 的id數組作為一個字段添加到了圖書中去。這樣的設計方式是在非關系型數據庫中常用的。在MongoDB中我們將與主鍵沒有直接關系的作者詳細信息單獨提取到另一個集合,用存儲主鍵的方式進行關聯查詢。當我們要查詢文章和作者時需要先查詢到所需的文章,再從文章作者中獲取作者id,最后獲得的完整的文章及其作者詳細信息。在這種情況下查詢性能顯然是不理想的,因為需要進行較多的關聯查詢。但當某位作者的信息需要修改時,范式化的維護優勢就凸顯出來了,我們無需考慮此作者關聯的圖書,直接進行修改此作者的字段即可。
1.2. 反范式化
1.2.1 反范式化的優點:
- 可以避免關聯,因為所有的數據幾乎都可以在一張表上顯示;
- 可以設計有效的索引;
1.2.2 反范式化的缺點:
- 表格內的冗余較多,刪除數據時候會造成表有些有用的信息丟失。
1.2.3 反范式化設計的例子:
以存儲一篇圖書及其作者為例,作者的信息包括作者的姓名,年齡,國籍。使用反范式化的設計如下:{
"_id" : ObjectId("5124b5d86041c7dca81917"),
"title" : "如何使用MongoDB",
"author" : [
{
"name" : "丁磊"
"age" : 40,
"nationality" : "china",
},
{
"name" : "馬雲"
"age" : 49,
"nationality" : "china",
},
{
"name" : "張召忠"
"age" : 59,
"nationality" : "china",
},
]
}
在這個示例中我們將作者的字段完全嵌入到了圖書中去,在查詢的時候直接查詢圖書即可獲得所對應作者的全部信息,但因一個作者可能有多本著作,當修改某位作者的信息時,我們需要遍歷所有圖書以找到該作者,將其修改。
1.3 范式化與反范式化混用
為了兼顧范式化與反范式化的優缺點,通常較常采用范式化與反范式化混合使用的方法,混合范式化與反范式化的設計如下:
{
"_id" : ObjectId("5124b5d86041c7dca81917"),
"title" : "如何使用MongoDB",
"author" : [
{
"_id" : ObjectId("144b5d83041c7dca84416"),
"name" : "丁磊"
},
{
"_id" : ObjectId("144b5d83041c7dca84418"),
"name" : "馬雲"
},
{
"_id" : ObjectId("144b5d83041c7dca84420"),
"name" : "張召忠"
},
]
}
這種方式是一種相對折中的方式,既保證了查詢效率,也保證的更新效率。但這樣的方式顯然要比前兩種較難以掌握,難點在於需要與實際業務進行結合來尋找合適的提取字段。
1.4 總結
- 范式化的更新效率是最高的,但查詢效率是最低的;
- 反范式化的查詢效率最高,但更新效率最低;
- 在實際的工作中我們需要根據自己實際的需要來設計表中的字段,以獲得最高的效率。
二. 填充因子的使用
填充因子(padding factor)是MongoDB為文檔的擴展而預留的增長空間,因為MongoDB的文檔是以順序表的方式存儲的,每個文檔之間會非常緊湊。
填充因子的理解之所以重要,是因為文檔的移動非常消耗性能,頻繁的移動會大大增加系統的負擔,在實際開發中最有可能會讓文檔體積變大的因素是數組,所以如果我們的文檔會頻繁修改並增大空間的話,則一定要充分考慮填充因子。
2.1 常用的兩種方法
2.1.1 增加初始分配空間
在集合的屬性中包含一個 usePowerOf2Sizes 屬性,當這個選項為true時,系統會將后續插入的文檔,初始空間都分配為2的N次方。
這種分配機制適用於一個數據會頻繁變更的集合使用,他會給每個文檔留有更大的空間,但因此空間的分配不會像原來那樣高效,如果你的集合在更新時不會頻繁的出現移動現象,這種分配方式會導致寫入速度相對變慢。
2.1.2 利用數據強行將初始分配空間擴大
db.book.insert({
"name" : "MongoDB",
"publishing" : "清華大學出版社",
"author" : "john",
"tags" : [],
"stuff" : "ggggggggggggggggggggggggggggggggggggg
ggggggggggggggggggggggggggggggggggggg
ggggggggggggggggggggggggggggggggggggg"
})
這樣看起來可能不太優雅,但有時卻很有效!當我們對這個文檔進行增長式修改時,只要將stuff字段刪掉即可。當然,這個stuff字段隨便你怎么起名,包括里邊的填充字符當然也是可以隨意添加的。
三. 索引的使用
索引對於一個數據庫的影響相信大家一定了解,如果一個查詢命令進入到數據庫中后,查詢優化器沒有找到合適的索引,那么數據庫會進行全集合掃描(在RDBMS中也叫全表掃描),全集合查詢對於性能的影響是災難性的。沒有索引的查詢就如同在詞典那毫無規律的海量詞匯中獲得某個你想要的詞匯,但這個詞典是沒有目錄的,只能通過逐頁來查找。這樣的查找可能會讓你耗費幾個小時的時間,但如果要求你查詢詞匯的頻率如同用戶訪問的頻率一樣的話。。。嘿嘿,我相信你一定會大喊“老子不干了!”。顯然計算機不會這樣喊,它一直是一個勤勤懇懇的員工,不論多么苛刻的請求他都會完成。所以請通過索引善待你的計算機。但使用索引有兩點需要注意:1. 索引越少越好;2. 索引顆粒越少越好。
3.1 索引越少越好
索引可以極大地提高查詢性能,那么索引是不是越多越好?答案是否定的,並且索引並非越多越好,而是越少越好。每當你建立一個索引時,系統會為你添加一個索引表,用於索引指定的列,然而當你對已建立索引的列進行插入或修改時,數據庫則需要對原來的索引表進行重新排序,重新排序的過程非常消耗性能,但應對少量的索引壓力並不是很大,但如果索引的數量較多的話對於性能的影響可想而知。所以在創建索引時需要謹慎建立索引,要把每個索引的功能都要發揮到極致,也就是說在可以滿足索引需求的情況下,索引的數量越少越好。
3.1 索引顆粒越少越好
什么叫顆粒越小越好?在索引列中每個數據的重復數量稱為顆粒,也叫作索引的基數。如果數據的顆粒過大,索引就無法發揮該有的性能。例如,我們擁有一個"age"列索引,如果在"age"列中,20歲占了50%,如果現在要查詢一個20歲,名叫"Tom"的人,我們則需要在表的50%的數據中查詢,索引的作用大大降低。所以,我們在建立索引時要盡量將數據顆粒小的列放在索引左側,以保證索引發揮最大的作用。
四. 尾聲
本文主要參考了以下兩篇博文: