性能與用戶量 “如何能讓軟件擁有更高的性能?”,我想這是一個大部分開發(fā)者都思考過的問題。性能往往決定了一個軟件的質(zhì)量,如果你開發(fā)的是一個互聯(lián)網(wǎng)產(chǎn)品,那么你的產(chǎn)品性能將更加受到考驗(yàn),因?yàn)槟忝鎸Φ氖菑V大的互聯(lián)網(wǎng)用戶,他們可不是那么有耐心的。嚴(yán)重點(diǎn)說,頁面的加載速度每增加一秒也許都會使你失去一部分用戶,也就是說,加載速度和用戶量是成反比的。那么用戶能夠接受的加載速度到底是多少呢? 如圖,如果頁面加載時間超過10s那么用戶就會離開,如果1s--10s的話就需要有提示,但如果我們的頁面沒有提示的話需要多快的加載速度呢?是的,1s 。 當(dāng)然,這是站在一個產(chǎn)品經(jīng)理的角度來說的,但如果站在一個技術(shù)人員的角度來說呢?加載速度和用戶量就是成正比的,你的用戶數(shù)量越多需要處理的數(shù)據(jù)當(dāng)然也就越多,加載速度當(dāng)然也就越慢。這是一件很有趣的事,所以如果你的產(chǎn)品如果是一件激動人心的產(chǎn)品,那么作為技術(shù)人員你需要做的事就是讓軟件的性能和用戶的數(shù)量同時增長,甚至性能增長要快于用戶量的增長。 數(shù)據(jù)庫性能對軟件整體性能的影響是不言而喻的,那么,當(dāng)我們使用MongoDB時改如何提高數(shù)據(jù)庫性能呢? 1.范式化與反范式化 在項目設(shè)計階段,明確集合的用途是對性能調(diào)優(yōu)非常重要的一步。 從性能優(yōu)化的角度來看,集合的設(shè)計我們需要考慮的是集合中數(shù)據(jù)的常用操作,例如我們需要設(shè)計一個日志(log)集合,日志的查看頻率不高,但寫入頻率卻很高,那么我們就可以得到這個集合中常用的操作是更新(增刪改)。如果我們要保存的是城市列表呢?顯而易見,這個集合是一個查看頻率很高,但寫入頻率很低的集合,那么常用的操作就是查詢。 對于頻繁更新和頻繁查詢的集合,我們最需要關(guān)注的重點(diǎn)是他們的范式化程度,在上篇范式化與反范式化的介紹中我們了解到,范式化與反范式化的合理運(yùn)用對于性能的提高至關(guān)重要。然而這種設(shè)計的使用非常靈活,假設(shè)現(xiàn)在我們需要存儲一篇圖書及其作者,在MongoDB中的關(guān)聯(lián)就可以體現(xiàn)為以下幾種形式: 1.完全分離(范式化設(shè)計) 示例1: ![]() { "_id" : ObjectId("5124b5d86041c7dca81917"), "title" : "如何使用MongoDB",
"author" : [
ObjectId("144b5d83041c7dca84416"),
ObjectId("144b5d83041c7dca84418"),
ObjectId("144b5d83041c7dca84420"),
]
} 我們將作者(comment) 的id數(shù)組作為一個字段添加到了圖書中去。這樣的設(shè)計方式是在非關(guān)系型數(shù)據(jù)庫中常用的,也就是我們所說的范式化設(shè)計。在MongoDB中我們將與主鍵沒有直接關(guān)系的圖書單獨(dú)提取到另一個集合,用存儲主鍵的方式進(jìn)行關(guān)聯(lián)查詢。當(dāng)我們要查詢文章和評論時需要先查詢到所需的文章,再從文章中獲取評論id,最后用獲得的完整的文章及其評論。在這種情況下查詢性能顯然是不理想的。但當(dāng)某位作者的信息需要修改時,范式化的維護(hù)優(yōu)勢就凸顯出來了,我們無需考慮此作者關(guān)聯(lián)的圖書,直接進(jìn)行修改此作者的字段即可。 2.完全內(nèi)嵌(反范式化設(shè)計) 示例2: ![]() { "_id" : ObjectId("5124b5d86041c7dca81917"), "title" : "如何使用MongoDB", "author" : [
{
"name" : "丁磊"
"age" : 40,
"nationality" : "china",
},
{
"name" : "馬云"
"age" : 49,
"nationality" : "china",
},
{
"name" : "張召忠"
"age" : 59,
"nationality" : "china",
},
]
} 在這個示例中我們將作者的字段完全嵌入到了圖書中去,在查詢的時候直接查詢圖書即可獲得所對應(yīng)作者的全部信息,但因一個作者可能有多本著作,當(dāng)修改某位作者的信息時時,我們需要遍歷所有圖書以找到該作者,將其修改。 3.部分內(nèi)嵌(折中方案) 示例3: ![]() { "_id" : ObjectId("5124b5d86041c7dca81917"), "title" : "如何使用MongoDB", "author" : [
{
"_id" : ObjectId("144b5d83041c7dca84416"),
"name" : "丁磊"
},
{
"_id" : ObjectId("144b5d83041c7dca84418"),
"name" : "馬云"
},
{
"_id" : ObjectId("144b5d83041c7dca84420"),
"name" : "張召忠"
},
]
} 這次我們將作者字段中的最常用的一部分提取出來。當(dāng)我們只需要獲得圖書和作者名時,無需再次進(jìn)入作者集合進(jìn)行查詢,僅在圖書集合查詢即可獲得。 這種方式是一種相對折中的方式,既保證了查詢效率,也保證的更新效率。但這樣的方式顯然要比前兩種較難以掌握,難點(diǎn)在于需要與實(shí)際業(yè)務(wù)進(jìn)行結(jié)合來尋找合適的提取字段。如同示例3所述,名字顯然不是一個經(jīng)常修改的字段,這樣的字段如果提取出來是沒問題的,但如果提取出來的字段是一個經(jīng)常修改的字段(比如age)的話,我們依舊在更新這個字段時需要大范圍的尋找并依此進(jìn)行更新。 在上面三個示例中,第一個示例的更新效率是最高的,但查詢效率是最低的,而第二個示例的查詢效率最高,但更新效率最低。所以在實(shí)際的工作中我們需要根據(jù)自己實(shí)際的需要來設(shè)計表中的字段,以獲得最高的效率。 2.理解填充因子 何為填充因子? 填充因子(padding factor)是MongoDB為文檔的擴(kuò)展而預(yù)留的增長空間,因?yàn)镸ongoDB的文檔是以順序表的方式存儲的,每個文檔之間會非常緊湊,如圖所示。 (注:圖片出處:《MongoDB The Definitive Guide》) 1.元素之間沒有多余的可增長空間。 2.當(dāng)我們對順序表中某個元素的大小進(jìn)行增長的時候,就會導(dǎo)致原來分配的空間不足,只能要求其向后移動。 3.當(dāng)修改元素移動后,后續(xù)插入的文檔都會提供一定的填充因子,以便于文檔頻繁的修改,如果沒有不再有文檔因增大而移動的話,后續(xù)插入的文檔的填充因子會依此減小。 填充因子的理解之所以重要,是因?yàn)槲臋n的移動非常消耗性能,頻繁的移動會大大增加系統(tǒng)的負(fù)擔(dān),在實(shí)際開發(fā)中最有可能會讓文檔體積變大的因素是數(shù)組,所以如果我們的文檔會頻繁修改并增大空間的話,則一定要充分考慮填充因子。 那么如果我們的文檔是個常常會擴(kuò)展的話,應(yīng)該如何提高性能? 兩種方案 1.增加初始分配空間。在集合的屬性中包含一個 usePowerOf2Sizes 屬性,當(dāng)這個選項為true時,系統(tǒng)會將后續(xù)插入的文檔,初始空間都分配為2的N次方。 這種分配機(jī)制適用于一個數(shù)據(jù)會頻繁變更的集合使用,他會給每個文檔留有更大的空間,但因此空間的分配不會像原來那樣高效,如果你的集合在更新時不會頻繁的出現(xiàn)移動現(xiàn)象,這種分配方式會導(dǎo)致寫入速度相對變慢。 2.我們可以利用數(shù)據(jù)強(qiáng)行將初始分配空間擴(kuò)大。 1 db.book.insert({2 "name" : "MongoDB",3 "publishing" : "清華大學(xué)出版社",4 "author" : "john"5 "tags" : []6 "stuff" : "ggggggggggggggggggggggggggggggggggggg7 ggggggggggggggggggggggggggggggggggggg8 ggggggggggggggggggggggggggggggggggggg"9 }) 是的,這樣看起來可能不太優(yōu)雅...但有時卻很有效!當(dāng)我們對這個文檔進(jìn)行增長式修改時,只要將stuff字段刪掉即可。當(dāng)然,這個stuff字段隨便你怎么起名,包括里邊的填充字符當(dāng)然也是可以隨意添加的。 3.準(zhǔn)確利用索引 索引對于一個數(shù)據(jù)庫的影響相信大家一定了解,如果一個查詢命令進(jìn)入到數(shù)據(jù)庫中后,查詢優(yōu)化器沒有找到合適的索引,那么數(shù)據(jù)庫會進(jìn)行全集合掃描(在RDBMS中也叫全表掃描),全集合查詢對于性能的影響是災(zāi)難性的。沒有索引的查詢就如同在詞典那毫無規(guī)律的海量詞匯中獲得某個你想要的詞匯,但這個詞典是沒有目錄的,只能通過逐頁來查找。這樣的查找可能會讓你耗費(fèi)幾個小時的時間,但如果要求你查詢詞匯的頻率如同用戶訪問的頻率一樣的話。。。嘿嘿,我相信你一定會大喊“老子不干了!”。顯然計算機(jī)不會這樣喊,它一直是一個勤勤懇懇的員工,不論多么苛刻的請求他都會完成。所以請通過索引善待你的計算機(jī):D。 在MongoDB中索引的類型與RDBMS中大體一致,我們不做過多重復(fù),我們來看一下在MongoDB中如何才能更高效的利用索引。 1.索引越少越好 索引可以極大地提高查詢性能,那么索引是不是越多越好?答案是否定的,并且索引并非越多越好,而是越少越好。每當(dāng)你建立一個索引時,系統(tǒng)會為你添加一個索引表,用于索引指定的列,然而當(dāng)你對已建立索引的列進(jìn)行插入或修改時,數(shù)據(jù)庫則需要對原來的索引表進(jìn)行重新排序,重新排序的過程非常消耗性能,但應(yīng)對少量的索引壓力并不是很大,但如果索引的數(shù)量較多的話對于性能的影響可想而知。所以在創(chuàng)建索引時需要謹(jǐn)慎建立索引,要把每個索引的功能都要發(fā)揮到極致,也就是說在可以滿足索引需求的情況下,索引的數(shù)量越少越好。 一. 隱式索引 //建立復(fù)合索引db.test.ensureIndex({"age": 1,"no": 1,"name": 1 }) 我們在查詢時可以迅速的將age,no字段進(jìn)行排序,隱式索引指的是如果我們想要排序的字段包含在已建立的復(fù)合索引中則無需重復(fù)建立索引。 db.test.find().sort("age": 1,"no": 1)
db.test.find().sort("age": 1) 如以上兩個排序查詢,均可使用上面的復(fù)合索引,而不需要重新建立索引。 二. 翻轉(zhuǎn)索引 //建立復(fù)合索引db.test.ensureIndex({"age": 1}) 翻轉(zhuǎn)索引很好理解,就是我們在排序查詢時無需考慮索引列的方向,例如這個例子中我們在查詢時可以將排序條件寫為"{'age': 0}",依舊不會影響性能。 2.索引列顆粒越小越好 什么叫顆粒越小越好?在索引列中每個數(shù)據(jù)的重復(fù)數(shù)量稱為顆粒,也叫作索引的基數(shù)。如果數(shù)據(jù)的顆粒過大,索引就無法發(fā)揮該有的性能。例如,我們擁有一個"age"列索引,如果在"age"列中,20歲占了50%,如果現(xiàn)在要查詢一個20歲,名叫"Tom"的人,我們則需要在表的50%的數(shù)據(jù)中查詢,索引的作用大大降低。所以,我們在建立索引時要盡量將數(shù)據(jù)顆粒小的列放在索引左側(cè),以保證索引發(fā)揮最大的作用。 |
|