數據庫驅動應用程序中影響性能的反模式

原文轉載自: [url][http://www.infoq.com/cn/articles/Anti-Patterns-Alois-Reitbauer/url]

幾乎所有現代應用程序都要通過數據庫實現數據持久化。數據庫訪問層經常要對嚴重的性能問題負責。一旦遇到數據庫的問題,大多數人開始研究數據庫本身。正確的索引和數據庫結構對提高性能非常關鍵。然而,很多時候糟糕的性能或可伸縮性問題的罪魁禍首卻是應用程序層,而不是數據庫。

應用程序層控制並驅動數據庫的訪問。這一層的問題不能從數據庫上得到補償。所以要想得到高性能和擴展性,數據訪問邏輯的設計非常關鍵。雖然數據庫驅動的應用程序中使用情況各不相同,但所有問題能夠歸結到幾個反模式上。分析你的應用程序中是否使用了下列的反模式,並且解決他們,能夠以最小的代價簡單讓你的軟件更快、 更穩定。

[b]對象/關係映射的誤用[/b]

對象/關係映射已經成爲現代數據庫應用程序的中心部分。對象/關係映射讓人從面向對象軟件中翻譯和訪問關係型數據的重擔中解脫出來。它們嚮應用程序人員隱藏了數據訪問大部分的複雜邏輯。由於開發人員更專注於實際的業務邏輯,而不是基礎架構細節,會使得生產效率更高。對象關係層不需要看到細節就可以輕鬆操作複雜的對象圖。這經常讓人產生錯誤的印象,認爲這些框架讓人從設計數據訪問邏輯的重擔中解脫了出來。

開發人員經常認爲數據訪問框架很容易就把一切搞定了;然而,不理解內部工作機制就使用對象/關係映射框架,很多時候會導致程序性能低下。主要有兩個誤解引起了這些問題──加載的行爲和加載的時間。

對象/關係映射基於每個對象加載數據。這意味着只有當一個對象被請求或者訪問時,需要的SQL語句纔會被創建並執行。這個原則非常普遍,乍一看多數情況下沒問題。但同時它也常常是性能和擴展性問題的原因所在。

讓我們看一個簡單的例子。在一個存儲地址信息的數據庫中,我們有一張表存儲人和一張表存儲地址。如果我們想得到每個人的名字及其居住的城市,我們不得不遍歷人那張表,然後訪問地址信息。下圖顯示了使用直接(out-of-the box)查詢機制的結果。可以看出,這個簡單的例子就導致了大量的數據庫查詢。

這直接引起了對象/關係映射中第二個重要的細節──加載時間。對象/關係映射-如果沒有事先告知-會盡量晚地加載數據。這一行爲就是延遲加載。延遲加載保證了數據儘可能晚地加載,目的是執行儘量少的數據庫查詢,同時避免創建不必要的對象。雖然這個方法通常情況下是可行的,但當它訪問那些沒有加載的數據,而數據連接已經不存在時,就可能導致嚴重的性能問題,以及所謂的LazyLoadingExceptions。

在如上所述的情況下,使用專門的數據查詢能夠顯著提高性能。

因此,雖然對象/關係映射在數據訪問的開發方面作用很大,設計合適的數據訪問邏輯的重擔仍然需要我們挑起。像dynaTrace這樣帶有工具的動態架構驗證,能夠幫助有效地識別程序中性能的弱點,並能主動解決。

[b]加載了太多數據,實際不需要這麼多[/b]

數據庫訪問中經常出現的另外一個反模式是加載了太多的數據,而實際上不需要這麼多。導致這樣的原因很多。快速應用程序開發工具提供了簡單的方式,能把數據結構和用戶接口控制連接起來。由於數據層由領域對象構成,通常它們包含的數據要比實際顯示的多得多。再次使用地址薄作爲例子。這一次需要顯示人的名字及其居住城市。兩個對象──地址和人──都被加載了,而不是隻加載這3個字段。這導致了數據庫、網絡和應用程序層的大量開銷。使用專門的查詢能夠大大減少查詢的數據量。然而這種性能的提升需要額外的工作去維護。表中新增一列可能需要對數據訪問層修改多處。

設計的服務接口不合理也經常引起這種反模式。服務接口通常要設計的很通用,以支持大量的用例。其好處是各種各樣的用例中都可以使用服務。另外,用例要比後臺服務實現變化的快得多。這會導致服務接口在某些場景下不適合。開發人員然後不得不使用一些補救方法,這可能導致數據訪問邏輯效率低下。這個問題在數據驅動的Web Services上經常出現。

爲了克服這些問題,開發過程中需要不斷地分析數據訪問模式。如果是敏捷開發方法,每個用戶故事完成後都應該檢查數據訪問邏輯。除此之外,應該跨應用程序用例分析數據訪問模式,以理解數據訪問邏輯,這樣能夠在開發中相應地優化數據訪問邏輯。

[b]未充分利用資源[/b]

數據庫是應用程序中資源的瓶頸,所以使用越少越好。通常情況下大家對數據庫連接的使用關注甚少。像任何共享的資源一樣,數據庫連接會嚴重影響整個系統的性能。尤其是web應用和使用對象/關係映射框架並用了延遲初始化的程序,會讓數據庫保持連接的時間比需要的更長。處理開始時獲得連接,直到頁面生成完成或者再也沒有數據訪問了才斷開。在使用對象/關係映射的應用程序中,連接經常保持着以避免可惡的延遲初始化的問題。通過重新設計數據訪問邏輯,把它從後處理(比如頁面生成)中分離出來,應用程序的性能和擴展性能得到極大的提高。

下圖展示了10個併發數據處理線程的反應時間。第一個使用了1個數據庫連接,第二個使用了2個連接,第三個使用了2個連接,但是有2/3的處理是在釋放連接之後執行的。第三個場景數據訪問經過更好的設計,僅用了1/10的資源就獲得了幾乎同樣高的性能。
[img]http://dl.iteye.com/upload/attachment/453803/990fcda7-4662-362c-a487-1445d5d0efce.png[/img]

[b]一刀切[/b]

一刀切是一種反模式,開發過程中經常見到,敏捷團隊中則更多。這種反模式的特徵是開發了主要功能之後,所有的數據訪問就同樣對待,好像它們沒有任何區別。然而,區別對待不同類型的數據和查詢可以顯著提高應用程序的性能和擴展性。

應該對數據進行分析,考慮其生命週期的特性。它是否經常變化,它是可修改的還是隻讀的呢?數據的訪問頻率和訪問模式,就隱含了一些潛在的代碼,比如可以做緩存。訪問頻率也暗含了一些線索,比如在哪裏做優化更有意義。這可以避免過早進行優化以及不必要的優化,保證了性能調優效果最好。

對數據的使用模式進行分析也有助於調整數據訪問層。理解真正使用了哪些數據有助於優化加載策略。比如,理解用戶怎樣瀏覽搜索結果對優化fetch size很有用。知道了用戶是否查看訂單詳細信息可以給訂單選擇延遲還是立即加載。

除數據之外,查詢也應該被分析並分類。重要的因素包括查詢時間、執行頻率、是否用於交互用戶的上下文或者批量處理的場景中。事務特性有助於更好地調整查詢的隔離級別。

比如,在同一個連接中運行用戶短暫的交互查詢和時間很長的報表查詢,很容易導致終端用戶的體驗很糟糕。報表查詢花費的時間很長,會佔用大量的數據庫連接,讓終端用戶的查詢無法拿到連接。通過給不同的查詢類型使用不同的數據庫連接池,會使終端用戶的性能更可預測。降低數據庫查詢中不需要的隔離級別,也能引起性能和擴展性的顯著提高。

[b]糟糕的測試[/b]

最後,缺少測試或者測試不正確是數據庫訪問應用程序性能和穩定性問題的一個主要原因。最近我曾就這一主題作了一個演講,並詢問聽衆是否把數據庫訪問看作應用程序中一個性能問題。雖然他們都贊成,但沒人有這樣的測試流程,來測試數據訪問的性能。所以雖然這個話題看上去是很重要,大家似乎都沒有花時間去做。然而,即使有測試流程,這也不一定說明測試就是正確的。雖然代碼完成後能夠立刻發現數據訪問邏輯中的很多問題,但通常很晚之後才執行測試,比如負載測試的時候。由於在生命週期的晚期才改動,可能需要修改架構,從而引起額外的開發和測試工作,這帶來了很高的不必要的代價。

而且,必須設計一些測試用例,來測試真實世界的數據訪問場景。測試數據訪問必須在併發模式下進行,並且使用不同的訪問類型。只有結合使用讀/寫訪問纔可能識別死鎖和併發的問題。除此之外,輸入的數據應該多種多樣,以避免數據庫訪問時經常命中緩存,這是不切合實際的。

很多時候人們對預期的負載知之甚少,也不知道去測試哪些負載。很不幸的是,根據我的經驗這種情況比比皆是。然而,不能把這當作藉口,不定義負載和性能標準。要知道,定義一些標準比一點也不定義要好得多。

如果你對性能數據真的毫無頭緒,最好是使用負載漸增測試法,逐步增加負載,直到達到了應用程序的最大值。這樣你就知道了應用程序的負載峯值。如果負載峯值既合理又現實,那就說明你做的不錯。否則你得知道在哪方面提高性能。大多數情況下初始的測試表明,應用程序能夠處理的負載要比期望的少得多。

[b]結論[/b]
數據庫訪問是影響現代應用程序性能和可伸縮性的一個關鍵點。雖然框架支持構建數據訪問邏輯,仍然需要對數據訪問邏輯投入相當的精力,以避免種種陷阱和問題。問題之關鍵是要理解應用程序數據訪問層的動態和特性的一切細節。

[b]譯者注[/b]

本文“未充分利用資源”一節中舉了這樣一個例子:第一個場景使用了1個數據庫連接,第二個場景使用了2個,第三個場景使用了2個,但是後處理(比如頁面生成)是在釋放數據庫連接之後進行。從圖表看出,第一個場景的反應時間最長,第三個場景的反應時間與第二個差不多,但是隻用了1/10的資源。

根據上下文,實際上應該是這樣的:第一個場景使用了1個連接,第二個使用了10個,第三個使用了1個,但是後處理(比如頁面生成)是在釋放數據庫連接之後進行。只有這樣,文中的結論才合理。

事實上,InfoQ總站該文後面Ray Davis有一個跟帖,就該問題提出了疑問,認爲第二個場景的2個連接應該爲10個。除了Ray Davis的疑問,譯者認爲,第三個場景的2個連接應該是1個,這樣上下文不矛盾了。

[b]譯者 張曉慶 [/b]
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章