第12期:存儲過程的利之弊
存儲過程是數據庫領域中應用非常廣泛的技術,關于它的利弊討論由來已久,我們這里針對存儲過程的兩個公認度較高的優點進行剖析,從而更清楚存儲過程的潛在風險及應用場景。
存儲過程利于界面與邏輯分離!
界面與邏輯分離是現代應用開發的一個基本準則。相對于后臺數據處理邏輯,界面會有更多樣性的環境,如PC、手機等,而且業務穩定性也不強,經常會改。如果能把兩者分離,開發和維護界面時綁著數據處理邏輯一起改,成本就低很多。
支持存儲過程的觀點認為,使用存儲過程能實現界面與邏輯分離。存儲過程在后臺數據庫中運算,只要向前端提供數據,而不必關心界面的形式和異動。把所有的數據處理邏輯都寫成存儲過程,還有利于統一數據的出入口,易于實現數據權限管控。
但是,仔細想想會發現,實現界面與邏輯分離并不是存儲過程的專利。只要做一個數據訪問層,所有數據的進出都通過這個訪問層,也會有同樣效果,事實上也確實有些應用是這么做的,但并不普遍。這是什么原因呢?這樣的數據訪問層和采用存儲過程有什么區別呢?
差別在于開發復雜度上。
數據處理邏輯會經常涉及到批量結構化數據的處理,而數據庫之外的程序設計語言在這方面能力都很弱,用Java寫個數據求和都要很多行,更不要說過濾、分組之類的運算了。而存儲過程的基本部件是SQL,這方面支持得很好,雖然存儲過程也有開發調試困難的毛病,但大多數情況下用于編寫復雜的數據處理邏輯還是要比高級語言更容易。
換句話說,存儲過程確實利于界面與邏輯分離,不過存儲過程實現后臺數據邏輯的優勢是SQL的集合運算能力支撐的。它主要來自于開發便捷,而不是應用結構。
有些場合無法利用存儲過程的計算能力,就只能實現庫外的數據訪問層了。比如數據來源涉及多數據庫或非數據庫的情況。
存儲過程利于界面與邏輯分離?
界面與邏輯分離的準則還有兩面性,它并沒有明確定義什么程序算是界面,更沒有說界面環節就不再有數據計算任務。
一個典型的任務就是報表。報表要在界面中呈現,其業務穩定性也較弱,經常增改,很顯然屬于界面環節的事務。但是,報表經常卻有復雜的數據源計算過程,如果把這部分計算也作為后臺邏輯強行放進存儲過程中,則不僅不會獲得界面與邏輯分離的好處,反而帶來巨大的麻煩,這與網上許多推薦將復雜報表的計算過程中采用存儲過程的觀點正好相反。
報表的呈現模板一般是由報表工具繪制的,以文件形式存放在應用中,如果數據源計算由存儲過程完成,則這兩個緊密相關的部分在物理上分別存放在兩處,要修改一張報表時需要兩個部分需要同步調整,不僅容易遺漏出錯,還可能增加溝通成本(兩部分的負責人員可能不同)。共享數據庫中的存儲過程還可能被其它報表甚至其它應用調用,修改時就可能造成其它模塊的不正常。用存儲過程實現報表數據源會破壞應用的模塊結構,增大應用的耦合度,造成維護成本升高。
采用存儲過程還會造成安全性和高效率的矛盾。原則上開發報表只需要對數據庫有只讀權限,但如果數據源是存儲過程開發的,則需要向報表開發人員開放編譯和運行存儲過程的權限,這幾乎可以對數據庫做一切操作了,安全隱患非常大。一個辦法是加強管理,所有上載的存儲過程都需要多人審核把關,但這勢必會導致低效率,本來報表開發人員自己就能完成的事情要涉及更多崗位。
如果有不依賴于數據庫的便捷計算能力,則可以避免掉存儲過程的這些劣勢。把業務穩定性不強、與界面相關緊密的計算移到數據庫外,和應用程序集成到一起,維護成本更低。即使業務穩定性強的計算邏輯也可以用庫外計算實現,解決多數據庫、非數據庫等多樣性數據源的問題。不采用存儲過程的整體應用結構更為合理。
存儲過程有更好的數據計算性能?
實際測試表明,用存儲過程實現數據計算,常常比用SQL取出數據后在外部計算的性能更好。存儲過程快在哪里了?
網上有觀點說,因為存儲過程是預編譯的,而每次執行SQL時要臨時編譯,所以存儲過程會更快。其實編譯SQL的那點時間相對于數據計算而言可以忽略不計,以不同參數反復執行的SQL也可以預先準備,只要編譯一次。有些程序員把不同參數拼進SQL,每次向數據庫發送不同SQL,編譯時間就不可忽略了。
存儲過程的快,主要在于數據不出庫。外部程序訪問庫內數據時必須通過數據庫提供的接口,而這些接口的性能大都不好,特別是面向Java程序的JDBC接口。每次發出SQL讓數據庫執行都會調用這個接口,速度就上不去。如果應用程序和數據庫不在同一臺物理機器上時,還會有一些網絡延遲,不過和接口的低性能相比并不算嚴重。在外部計算時,從數據庫獲取數據的時間常常會超過計算本身的時間。
存儲過程本身的執行性能并不好。我們針對某著名商用數據庫進行過測試:一句SQL可以完成的運算(比如對某個大表的字段求和),如果改用存儲過程把數據一行行取出來計算,差不多會慢出一個數量級。用Java等語言從文件系統中讀數做同樣的計算,也會比存儲過程快很多;外部計算相對容易寫出并行代碼,充分利用現代服務器多CPU的優勢,存儲過程一般都沒有這個機制了。而且,如果把很多計算都放到存儲過程中,并發運算時會加重數據庫的負擔,使本來就不快的存儲過程更慢。
存儲過程的性能更好,與其說是優勢,倒不如說是被低效的數據庫訪問接口綁架所致。
目前業內還只有關系數據庫有較好的交易一致性能力,適合充當OLTP業務的后臺,這樣從前端采集到的數據會直接進入關系數據庫,這導致原始數據大量存儲于數據庫中。如果要對這些數據進行計算,采用外部計算方案時,取出數據太慢,總體性能就會很差;而使用存儲過程,雖然計算本身不快,但數據不出庫也會獲得較好性能。這是存儲過程不能被完全替代的主要原因和場景。