數(shù)據(jù)庫優(yōu)化與應(yīng)用程序性能的五個(gè)平衡點(diǎn)
我們經(jīng)常提到數(shù)據(jù)庫優(yōu)化,經(jīng)常為提高應(yīng)用程序性能對(duì)數(shù)據(jù)庫一陣折騰,但這真的有效嗎?我們是否真的看清哪些問題出在數(shù)據(jù)庫方面,哪些問題出在應(yīng)用程序方面?
幾乎所有現(xiàn)代應(yīng)用程序都要通過數(shù)據(jù)庫實(shí)現(xiàn)數(shù)據(jù)持久化。數(shù)據(jù)庫訪問層經(jīng)常要對(duì)嚴(yán)重的性能問題負(fù)責(zé)。一旦遇到數(shù)據(jù)庫的問題,大多數(shù)人開始研究數(shù)據(jù)庫本身。正確的索引和數(shù)據(jù)庫結(jié)構(gòu)對(duì)提高性能非常關(guān)鍵。然而,很多時(shí)候糟糕的性能或可伸縮性問題的罪魁禍?zhǔn)讌s是應(yīng)用程序?qū)樱皇菙?shù)據(jù)庫。
應(yīng)用程序?qū)涌刂撇Ⅱ?qū)動(dòng)數(shù)據(jù)庫的訪問。這一層的問題不能從數(shù)據(jù)庫上得到補(bǔ)償。所以要想得到高性能和擴(kuò)展性,數(shù)據(jù)訪問邏輯的設(shè)計(jì)非常關(guān)鍵。雖然數(shù)據(jù)庫驅(qū)動(dòng)的應(yīng)用程序中使用情況各不相同,但所有問題能夠歸結(jié)到幾個(gè)反模式上。分析你的應(yīng)用程序中是否使用了下列的反模式,并且解決他們,能夠以最小的代價(jià)簡(jiǎn)單讓你的軟件更快、 更穩(wěn)定。
對(duì)象/關(guān)系映射的誤用
對(duì)象/關(guān)系映射已經(jīng)成為現(xiàn)代數(shù)據(jù)庫應(yīng)用程序的中心部分。對(duì)象/關(guān)系映射讓人從面向?qū)ο筌浖蟹g和訪問關(guān)系型數(shù)據(jù)的重?fù)?dān)中解脫出來。它們向應(yīng)用程序人員隱藏了數(shù)據(jù)訪問大部分的復(fù)雜邏輯。由于開發(fā)人員更專注于實(shí)際的業(yè)務(wù)邏輯,而不是基礎(chǔ)架構(gòu)細(xì)節(jié),會(huì)使得生產(chǎn)效率更高。對(duì)象關(guān)系層不需要看到細(xì)節(jié)就可以輕松操作復(fù)雜的對(duì)象圖。這經(jīng)常讓人產(chǎn)生錯(cuò)誤的印象,認(rèn)為這些框架讓人從設(shè)計(jì)數(shù)據(jù)訪問邏輯的重?fù)?dān)中解脫了出來。
開發(fā)人員經(jīng)常認(rèn)為數(shù)據(jù)訪問框架很容易就把一切搞定了;然而,不理解內(nèi)部工作機(jī)制就使用對(duì)象/關(guān)系映射框架,很多時(shí)候會(huì)導(dǎo)致程序性能低下。主要有兩個(gè)誤解引起了這些問題──加載的行為和加載的時(shí)間。
對(duì)象/關(guān)系映射基于每個(gè)對(duì)象加載數(shù)據(jù)。這意味著只有當(dāng)一個(gè)對(duì)象被請(qǐng)求或者訪問時(shí),需要的SQL語句才會(huì)被創(chuàng)建并執(zhí)行。這個(gè)原則非常普遍,乍一看多數(shù)情況下沒問題。但同時(shí)它也常常是性能和擴(kuò)展性問題的原因所在。
讓我們看一個(gè)簡(jiǎn)單的例子。在一個(gè)存儲(chǔ)地址信息的數(shù)據(jù)庫中,我們有一張表存儲(chǔ)人和一張表存儲(chǔ)地址。如果我們想得到每個(gè)人的名字及其居住的城市,我們不得不遍歷人那張表,然后訪問地址信息。下圖顯示了使用直接(out-of-the box)查詢機(jī)制的結(jié)果。可以看出,這個(gè)簡(jiǎn)單的例子就導(dǎo)致了大量的數(shù)據(jù)庫查詢。
這直接引起了對(duì)象/關(guān)系映射中第二個(gè)重要的細(xì)節(jié)──加載時(shí)間。對(duì)象/關(guān)系映射-如果沒有事先告知-會(huì)盡量晚地加載數(shù)據(jù)。這一行為就是延遲加載。延遲加載保證了數(shù)據(jù)盡可能晚地加載,目的是執(zhí)行盡量少的數(shù)據(jù)庫查詢,同時(shí)避免創(chuàng)建不必要的對(duì)象。雖然這個(gè)方法通常情況下是可行的,但當(dāng)它訪問那些沒有加載的數(shù)據(jù),而數(shù)據(jù)連接已經(jīng)不存在時(shí),就可能導(dǎo)致嚴(yán)重的性能問題,以及所謂的LazyLoadingExceptions。
在如上所述的情況下,使用專門的數(shù)據(jù)查詢能夠顯著提高性能。
因此,雖然對(duì)象/關(guān)系映射在數(shù)據(jù)訪問的開發(fā)方面作用很大,設(shè)計(jì)合適的數(shù)據(jù)訪問邏輯的重?fù)?dān)仍然需要我們挑起。像dynaTrace這樣帶有工具的動(dòng)態(tài)架構(gòu)驗(yàn)證,能夠幫助有效地識(shí)別程序中性能的弱點(diǎn),并能主動(dòng)解決。
加載了太多數(shù)據(jù),實(shí)際不需要這么多
數(shù)據(jù)庫訪問中經(jīng)常出現(xiàn)的另外一個(gè)反模式是加載了太多的數(shù)據(jù),而實(shí)際上不需要這么多。導(dǎo)致這樣的原因很多。快速應(yīng)用程序開發(fā)工具提供了簡(jiǎn)單的方式,能把數(shù)據(jù)結(jié)構(gòu)和用戶接口控制連接起來。由于數(shù)據(jù)層由領(lǐng)域?qū)ο髽?gòu)成,通常它們包含的數(shù)據(jù)要比實(shí)際顯示的多得多。再次使用地址薄作為例子。這一次需要顯示人的名字及其居住城市。兩個(gè)對(duì)象──地址和人──都被加載了,而不是只加載這3個(gè)字段。這導(dǎo)致了數(shù)據(jù)庫、網(wǎng)絡(luò)和應(yīng)用程序?qū)拥拇罅块_銷。使用專門的查詢能夠大大減少查詢的數(shù)據(jù)量。然而這種性能的提升需要額外的工作去維護(hù)。表中新增一列可能需要對(duì)數(shù)據(jù)訪問層修改多處。
設(shè)計(jì)的服務(wù)接口不合理也經(jīng)常引起這種反模式。服務(wù)接口通常要設(shè)計(jì)的很通用,以支持大量的用例。其好處是各種各樣的用例中都可以使用服務(wù)。另外,用例要比后臺(tái)服務(wù)實(shí)現(xiàn)變化的快得多。這會(huì)導(dǎo)致服務(wù)接口在某些場(chǎng)景下不適合。開發(fā)人員然后不得不使用一些補(bǔ)救方法,這可能導(dǎo)致數(shù)據(jù)訪問邏輯效率低下。這個(gè)問題在數(shù)據(jù)驅(qū)動(dòng)的Web Services上經(jīng)常出現(xiàn)。
為了克服這些問題,開發(fā)過程中需要不斷地分析數(shù)據(jù)訪問模式。如果是敏捷開發(fā)方法,每個(gè)用戶故事完成后都應(yīng)該檢查數(shù)據(jù)訪問邏輯。除此之外,應(yīng)該跨應(yīng)用程序用例分析數(shù)據(jù)訪問模式,以理解數(shù)據(jù)訪問邏輯,這樣能夠在開發(fā)中相應(yīng)地優(yōu)化數(shù)據(jù)訪問邏輯。
未充分利用資源
數(shù)據(jù)庫是應(yīng)用程序中資源的瓶頸,所以使用越少越好。通常情況下大家對(duì)數(shù)據(jù)庫連接的使用關(guān)注甚少。像任何共享的資源一樣,數(shù)據(jù)庫連接會(huì)嚴(yán)重影響整個(gè)系統(tǒng)的性能。尤其是web應(yīng)用和使用對(duì)象/關(guān)系映射框架并用了延遲初始化的程序,會(huì)讓數(shù)據(jù)庫保持連接的時(shí)間比需要的更長(zhǎng)。處理開始時(shí)獲得連接,直到頁面生成完成或者再也沒有數(shù)據(jù)訪問了才斷開。在使用對(duì)象/關(guān)系映射的應(yīng)用程序中,連接經(jīng)常保持著以避免可惡的延遲初始化的問題。通過重新設(shè)計(jì)數(shù)據(jù)訪問邏輯,把它從后處理(比如頁面生成)中分離出來,應(yīng)用程序的性能和擴(kuò)展性能得到極大的提高。
下圖展示了 。第一個(gè)使用了1個(gè)數(shù)據(jù)庫連接,第二個(gè)使用了2個(gè)連接,第三個(gè)使用了2個(gè)連接,但是有2/3的處理是在釋放連接之后執(zhí)行的。第三個(gè)場(chǎng)景數(shù)據(jù)訪問經(jīng)過更好的設(shè)計(jì),僅用了1/10的資源就獲得了幾乎同樣高的性能。
一刀切
一刀切是一種反模式,開發(fā)過程中經(jīng)常見到,敏捷團(tuán)隊(duì)中則更多。這種反模式的特征是開發(fā)了主要功能之后,所有的數(shù)據(jù)訪問就同樣對(duì)待,好像它們沒有任何區(qū)別。然而,區(qū)別對(duì)待不同類型的數(shù)據(jù)和查詢可以顯著提高應(yīng)用程序的性能和擴(kuò)展性。
應(yīng)該對(duì)數(shù)據(jù)進(jìn)行分析,考慮其生命周期的特性。它是否經(jīng)常變化,它是可修改的還是只讀的呢?數(shù)據(jù)的訪問頻率和訪問模式,就隱含了一些潛在的代碼,比如可以做緩存。訪問頻率也暗含了一些線索,比如在哪里做優(yōu)化更有意義。這可以避免過早進(jìn)行優(yōu)化以及不必要的優(yōu)化,保證了性能調(diào)優(yōu)效果最好。
對(duì)數(shù)據(jù)的使用模式進(jìn)行分析也有助于調(diào)整數(shù)據(jù)訪問層。理解真正使用了哪些數(shù)據(jù)有助于優(yōu)化加載策略。比如,理解用戶怎樣瀏覽搜索結(jié)果對(duì)優(yōu)化fetch size很有用。知道了用戶是否查看訂單詳細(xì)信息可以給訂單選擇延遲還是立即加載。
除數(shù)據(jù)之外,查詢也應(yīng)該被分析并分類。重要的因素包括查詢時(shí)間、執(zhí)行頻率、是否用于交互用戶的上下文或者批量處理的場(chǎng)景中。事務(wù)特性有助于更好地調(diào)整查詢的隔離級(jí)別。
比如,在同一個(gè)連接中運(yùn)行用戶短暫的交互查詢和時(shí)間很長(zhǎng)的報(bào)表查詢,很容易導(dǎo)致終端用戶的體驗(yàn)很糟糕。報(bào)表查詢花費(fèi)的時(shí)間很長(zhǎng),會(huì)占用大量的數(shù)據(jù)庫連接,讓終端用戶的查詢無法拿到連接。通過給不同的查詢類型使用不同的數(shù)據(jù)庫連接池,會(huì)使終端用戶的性能更可預(yù)測(cè)。降低數(shù)據(jù)庫查詢中不需要的隔離級(jí)別,也能引起性能和擴(kuò)展性的顯著提高。
糟糕的測(cè)試
最后,缺少測(cè)試或者測(cè)試不正確是數(shù)據(jù)庫訪問應(yīng)用程序性能和穩(wěn)定性問題的一個(gè)主要原因。最近我曾就這一主題作了一個(gè)演講,并詢問聽眾是否把數(shù)據(jù)庫訪問看作應(yīng)用程序中一個(gè)性能問題。雖然他們都贊成,但沒人有這樣的測(cè)試流程,來測(cè)試數(shù)據(jù)訪問的性能。所以雖然這個(gè)話題看上去是很重要,大家似乎都沒有花時(shí)間去做。然而,即使有測(cè)試流程,這也不一定說明測(cè)試就是正確的。雖然代碼完成后能夠立刻發(fā)現(xiàn)數(shù)據(jù)訪問邏輯中的很多問題,但通常很晚之后才執(zhí)行測(cè)試,比如負(fù)載測(cè)試的時(shí)候。由于在生命周期的晚期才改動(dòng),可能需要修改架構(gòu),從而引起額外的開發(fā)和測(cè)試工作,這帶來了很高的不必要的代價(jià)。
而且,必須設(shè)計(jì)一些測(cè)試用例,來測(cè)試真實(shí)世界的數(shù)據(jù)訪問場(chǎng)景。測(cè)試數(shù)據(jù)訪問必須在并發(fā)模式下進(jìn)行,并且使用不同的訪問類型。只有結(jié)合使用讀/寫訪問才可能識(shí)別死鎖和并發(fā)的問題。除此之外,輸入的數(shù)據(jù)應(yīng)該多種多樣,以避免數(shù)據(jù)庫訪問時(shí)經(jīng)常命中緩存,這是不切合實(shí)際的。
很多時(shí)候人們對(duì)預(yù)期的負(fù)載知之甚少,也不知道去測(cè)試哪些負(fù)載。很不幸的是,根據(jù)我的經(jīng)驗(yàn)這種情況比比皆是。然而,不能把這當(dāng)作借口,不定義負(fù)載和性能標(biāo)準(zhǔn)。要知道,定義一些標(biāo)準(zhǔn)比一點(diǎn)也不定義要好得多。
如果你對(duì)性能數(shù)據(jù)真的毫無頭緒,最好是使用負(fù)載漸增測(cè)試法,逐步增加負(fù)載,直到達(dá)到了應(yīng)用程序的最大值。這樣你就知道了應(yīng)用程序的負(fù)載峰值。如果負(fù)載峰值既合理又現(xiàn)實(shí),那就說明你做的不錯(cuò)。否則你得知道在哪方面提高性能。大多數(shù)情況下初始的測(cè)試表明,應(yīng)用程序能夠處理的負(fù)載要比期望的少得多。
結(jié)論
數(shù)據(jù)庫訪問是影響現(xiàn)代應(yīng)用程序性能和可伸縮性的一個(gè)關(guān)鍵點(diǎn)。雖然框架支持構(gòu)建數(shù)據(jù)訪問邏輯,仍然需要對(duì)數(shù)據(jù)訪問邏輯投入相當(dāng)?shù)木Γ员苊夥N種陷阱和問題。問題之關(guān)鍵是要理解應(yīng)用程序數(shù)據(jù)訪問層的動(dòng)態(tài)和特性的一切細(xì)節(jié)。
【編輯推薦】