應用程序需要使用存儲過程?

應用程序需要使用存儲過程?

問題的提出

    當你在開發一個基於數據庫的應用的時候,你可能會想這樣一個問題:數據庫有關的事務操作部分因該放在那裏?是以存儲過程(stored procedure)的形式放在數據庫端呢,還是將查詢以及相應運算嵌在應用程序當中呢?要回答這樣一個問題,你首先要了解存儲過程以及內嵌T-SQL這兩種方案各自的優缺點以及他們分別適用的場合。尤其是在新的.NET開發環境中,如何選取正確的解決方案是非常關鍵的。

爲什麼要使用存儲過程(Stored Procedures)?

    你可能對數據庫編程已經有一些經驗了,對SqlCommand對象也比較熟悉了。但是你是否想過你的這些數據庫相關的操作是不是符合優化原則呢?那些相對複雜的數據處理是應當嵌在應用程序中呢,還是把它封裝在存儲過程中,放置在數據庫端?
    再展開討論之前,讓我們先簡要的回顧一下存儲過程的概念。
    存儲過程是一組T-SQL語句,它們存放在一起形成一段T-SQL程序。在運行的時候,你可以傳入一些參數;你得到的可以是結果集合(result set),也可以是輸出參數(output parameters),甚至是返回值(return value)。存儲過程在第一次被執行的時候,數據庫系統要首先對它進行分析和編譯。編譯後得到了一個執行計劃(execution plan)。所謂執行計劃就是數據庫具體執行這個存儲過程的先後步驟的過程紀錄。這個編譯得到的執行計劃被放置到數據庫的緩存池中以備以後再次使用。如果這個存儲過程今後再次被調用,那麼數據庫將從緩存池中取出這個執行計劃來運行。這樣就避免了重複對該存儲過程進行再次分析和編譯,從而提高了數據庫的性能。(這些緩存池中的執行計劃將一直被保存着,直到數據庫重新啓動或是系統內存不夠用而被清除出緩存池)。
    是否使用存儲過程,我們可以從以下三個方面來進行分析。

一.性能(Performance)
    在以前,存儲過程比查詢(Query)有性能上的優勢。原因是數據庫會緩存存儲過程的執行計劃(execution plan)。但是在最近的SQL數據庫7.0以後的版本中,查詢的執行計劃也被數據庫放到緩存池中。這樣一來存儲過程的傳統優勢就不再存在了。只要你的查詢語句是靜態的(static)並且你經常使用它,這樣他就不會被數據庫清理出緩存池去。如果它的執行計劃得到重用,那麼理論上講查詢和存儲過程沒有性能上的區別。要注意的一點是,查詢的語句必須保持靜態,如果你更動了一些,哪怕是很不重要的一個部分,那麼這個查詢很可能在緩存池裏找不到匹配的執行計劃。那麼查詢只好被數據庫重新編譯,這將導致性能上的損失。
    不過在網絡傳輸方面,存儲過程比查詢仍然佔有優勢。因爲使用存儲過程只需要向數據庫傳遞存儲過程的名字和必要的參數,而不是像查詢那樣要傳輸全部查詢語句。如果查詢邏輯複雜的話,那麼查詢語句的大小也將會比較可觀。另外,設計合理的存儲過程可以減少客戶端和數據庫端之間的往返,甚至減少到一次。
    另外,通過使用遠端過程調用(remote procedure calls, RPC)來執行數據庫端的存儲過程可以加強執行計劃的重用性,從而提高性能。當你指定SqlCommand.CommandType爲StoredProcedure的時候,存儲過程是通過RPC來執行的。RPC包裝整理參數然後調用數據庫的存儲過程的方式使得數據庫引擎非常容易發現匹配的執行計劃。你在調用該存儲過程時可以使用了不同的參數,數據庫系統將會使用同一個執行計劃的。
    在決定是否使用存儲過程的時候,你還要判斷你的特定操作是不是利用了存儲過程的長處。總體來說:

" 基於集合的運算(Set-Based)是T-SQL的強項
" 基於行的運算(Row-Based)以及基於字符串的運算(String manipulation)不是T-SQL的強項。至少在下一個版本Yukon數據庫出來之前,你應當避免這樣的操作。

    也就是說,有些操作由應用程序的高級語言來完成往往會比數據庫來往成更有效。比如比較複雜的字符串處理。這時候,將所有的操作全部放到存儲過程中就不是一個最優化的辦法。你可能要合理的劃分任務,讓數據庫和應用程序各自完成其擅長的任務。

二.可維護性和抽象能力(Maintainability and Abstraction)
    使用存儲過程另外一個潛在的好處就是可維護性好。儘管我們希望數據庫結構永遠不要變動,事務處理規則也永遠保持不變,但事實上這是不大可能發生的。對於好多更動,你也許只需要更改存儲過程的具體實現就可以完成。所有使用它的客戶端程序就不需要重新修改,調試和編譯。這樣很多變動對於客戶程序來說就是透明的(transparent)。在大多情況下,這種辦法往往是最有效和最簡單的。
    另外,通過抽象具體實現(implementation)和將T-SQL語句放在存儲過程中,可以使任何客戶端調用者以一個統一的形式來訪問數據。從全局上看,一種數據操作運算只有一種實現,放在一個地方。這樣更改和維護都將非常方便。不同的用戶也將永遠得到同一樣的結果。
    使用存儲過程的另外一個維護性方面的好處是你可以有更好的程序版本控制。你可以使用版本控制的軟件來幫助你維護存儲過程,就象你維護其它源程序那樣。比如,你可以使用微軟的Visual SourceSafe?來幫你做到這一點。這樣你可以很方便的找回以前任何一個版本的存儲過程。
    需要指出的一點是使用存儲過程不能防止你修改數據庫結構和事務處理規則。如果更動比較大,需要重新設計傳入的參數或者返回值,那麼你將需要修改客戶端調用這些存儲過程的程序段。
    你應該考慮到使用存儲過程來封裝你的事務處理邏輯將影響應用的可移植性。存儲過程是和SQL數據庫捆綁在一起的,如果你想更換數據庫平臺,你可能要重寫這些存儲過程。如果可移植性對你的應用的是非常關鍵的,那麼將事務處理邏輯放在數據庫系統中立(RDBMS-neutral)的中間層(middle-tier)比較好。

三.安全性(Security)

    最後一個使用存儲過程的原因是它可以增強數據庫系統的安全性。
    從管理用戶訪問信息角度來講,它可以通過讓用戶訪問一定的存儲過程來保證用戶可以訪問特定的數據,這是一種間接的數據訪問,而不是直接對用戶開放式據庫表格。其實我們可以將存儲過程假想爲數據庫系統的View。唯一的區別就是存儲過程可以變更參數而使得結果動態變化。
    存儲過程還可以讓你在程序安全性方面有所改進。它可以防備一種叫做SQL注入式的攻擊(SQL injection attacks)- 這種攻擊主要是用AND或是OR運算符將命令拼接在有效的輸入參數之後。存儲過程還可以隱藏事務處理規則於數據庫端,而不是放在客戶程序端。在有些情況下(比如涉及到知識產權等等),這種隱藏是非常重要的。
    此外,使用存儲過程可以讓你使用ADO.NET提供的SqlParameter類。你可以使用這個類來說明具體的參數類型。這使得你更加容易來驗證用戶輸入的參數是否合法。參數對於存儲過程和in-line查詢來說是同樣重要的,它可以將用戶的輸入降低到一個很小的範圍內。
    當然使用存儲過程並不意味着安全方面你可以高枕無憂了。事實上,不好的程序以及數據庫管理方面的漏洞仍然可以將你置於可能的攻擊之中。如果對SQL數據庫的角色(Role)創建以及授權不當,那麼將導致用戶可以訪問一些他們本不應該訪問的數據。同樣,僅僅使用存儲過程並不能完全保證不受SQL注入式攻擊。
    另外使用SqlParameter類來校驗用戶輸入也不是絕對安全的,不管是在後臺T-SQL寫的存儲過程中還是嵌在應用中的查詢,所有用戶的輸入,尤其是字符串類型的數據,一定要在交給數據庫引擎處理之前進行有效性的校驗。

存儲過程適用於你嗎?

    綜上所述,使用存儲過程有如下幾個突出優點:

" 提高新能,減少了網絡流量
" 在數據庫端一點的維護(single point of maintenance )
" 抽象和概化業務邏輯,增強了一致性和安全性
" 減少了一些可能的惡意攻擊的機會
" 鼓勵執行計劃的重用性(Encourage execution plan re-use )

    如果你的應用程序能有效的利用存儲過程的上述優點,那麼你就應該儘量使用。但是如果你的應用要求有很高的可移植性,或者數據庫的結構變動很大,不能相對穩定下來,那麼你可能要試一試其他方法了。比如你現在在SQL數據庫上爲用戶開發一個早期可行性驗證程序,今後用戶很可能使用MySQL或是Oracle等其它數據庫,那麼你就因該避免使用SQL數據庫的存儲過程,而使用程序內嵌的數據庫操作語句。這樣當你更換數據庫平臺的時候,可以極大的保證程序不受影響。
    另外,你還要考慮使用存儲過程的技術問題。也許你和你的手下非常不熟悉存儲過程編程,並且沒有時間去很快掌握它。這些因素你也需要通盤考慮。另外如前所述,數據庫存儲過程擅長於基於集合(set-based)的操作,而不擅長基於行(row-based)的操作。如果你對存儲過程沒有很好的瞭解,而不正確的使用了它往往會導致很不好的執行性能。所以如果你決定使用存儲過程,那麼多花一些時間來學習它是很有必要的。

Visual Studio .NET 提供的工具

    微軟的Visual Studio? .NET提供了一些工具來幫助你察看和操作SQL數據庫端的存儲過程(以及其它一些數據庫對象)。現在就讓我們大概來看一下這些常用的工具。

察看存儲過程(Viewing Stored Procedures)
    你可以使用服務器瀏覽器(Server Explorer)來查看已有的存儲過程,你可以看它們要求的參數,具體的實現細節等等。如果你已經連接到數據庫上,你可以一級一級打開,直到看到你需要的存儲過程(如下圖1所示)。並且有趣的是,這些存儲過程的數據類型已經被轉換爲ADO類型。你可以查看.NET的文檔去搞清楚SQL數據庫數據類型和.NET間數據類型的映射關係。當然了,如果你在ADO.NET使用參數,你應該使用SqlDbType提供的枚舉了型,例如:


SqlDbType.DateTime
SqlDbType.NVarChar
Sqldbtype.Money
SqlDbType.Image


圖 1

    如果你雙擊一個存儲過程,Visual Studio就會打開它讓你編輯。編輯窗口使用了顏色來顯示T-SQL的關鍵字。另外,由於你是在編輯而不是創建一個存儲過程,所以你看到的不是CREATE PROCEDURE語法,而是ALTER PROCEDURE。

創建和修改存儲過程(Creating and Modifying Stored Procedures)
    你首先要弄清楚你是否有權限來創建和修改存儲過程。如果沒有的話,你需要向數據庫管理員申請。
    在服務器瀏覽器的窗口之中,在存儲過程節點上單擊鼠標右鍵,然後選擇New Stored Procedure就可以創建新的存儲過程(如下圖2所示)。同樣你可以修改已有的存儲過程。


圖 2

    如果你需要一些幫助來創建你的存儲過程,在存儲過程窗口單擊鼠標右鍵,然後選擇Insert SQL;或者你選擇一段T-SQL語句,然後右擊,選擇Design SQL Block。這樣你就可以打開查詢構造窗口(Query Builder window)。這個圖形化的查詢構造工具可以極大的方便你構造複雜的查詢。在這裏完成工作後,你可以把得到的查詢語句拷貝到你的存儲過程中去。


圖 3

    在現在的Visual Studio 2003版中,存儲過程的編寫窗口還沒有"智能(IntelliSense)"。所以你可能還要打開SQL數據庫的在線幫助,來查詢一些語法,函數等等。要注意的是,只有語法完全正確的存儲過程纔可以保存到數據庫中。
    當你完成了存儲過程的創建以後,你可以馬上測試它。方法很簡單,右擊這個存儲過程,然後選擇Run Stored Procedure。

使用存儲過程的一些經驗

    這裏有一些關於使用存儲過程的最基本的常識。正確的使用這些常識可以幫助你避免一些常犯的錯誤,寫出效率更高的程序來。

使用 SET NOCOUNT ON
    在缺省情況下,存儲過程返回被該存儲過程影響的表的行數(Rows),然而在大多情況下,這一信息是不重要的,大多數程序也不使用這一信息。使用SET NOCOUNT ON語句將停止這一行爲。這將減少一次或多次客戶端和數據庫間的往返(具體數量取決於這一存儲過程包含有多少語句影響行數(affect rows))。通常來說,這是一個小的開銷,問題不大。但是對於流量很大的應用卻可能會造成比較大的影響,所以建議使用SET NOCOUNT ON。具體語法如下所示:

 

create procedure test_MyStoredProc @param1 int
as
set nocount on



不要使用 sp_ 前綴(sp_prefix)
    sp_前綴是SQL數據庫的保留字,是專用於系統存儲過程的。數據庫的引擎會首先在主控數據庫(Master Database)中尋找有這個前綴的存儲過程。也就是說在搜尋完主控數據庫後,如果沒有發現該名稱的存儲過程纔會搜尋用戶具體適用的數據庫,這顯然增加了搜索的時間。如果主控數據庫中有和你定義的存儲過程同名的存儲過程,那麼主控數據庫的那個將會被執行,而你的則會被忽略。

保守的使用條件參數(Optional Parameters)
    在使用條件參數的時候一定要考慮清楚。如果使用不當則很可能會嚴重的影響性能。如果執行不是根據傳入的參數的組合的話,你就不必要使用它。你可以在存儲過程中使用條件語句(conditional coding)來達到類似的效果。當然要列舉所有可能的參數組合並一一給出對應的執行語句段是比較繁複和比較容易出錯的。使用時一定要小心。

儘量使用輸出(OUTPUT)參數
    如果你的存儲過程只返回標量值(scalar data),那麼使用輸出參數會提高執行的效能。原因是使用使用輸出參數避免了生成一個Result set對象,從而減少了開銷,提高了速度。(另外你還可以使用輸出參數來返回一個cursor。在什麼情況下使用這個方案更有效是一個比較複雜的問題,留待以後有機會再具體討論)

提供返回值(RETURN Value)使用返回值來回傳存儲過程的執行結果是一個比較好的做法。如果在你所在的開發小組中大家能事先商定一套返回值來代表不同的執行結果,那麼將會極大的便利各種錯誤處理,並可能給前臺用戶顯示一些更有意義的錯誤信息。

先使用DDL, 然後再使用DML
    如果數據庫定義語句(data definition language,DDL)在數據庫操作語句(data manipulation language, DML)之前,那麼數據庫將重新編譯存儲過程。這樣保證DML使用的是DDL剛剛修改過的對象。所以說如果你將DDL語句統統放在DML語句之前,那麼數據庫只要重新編譯一次存儲過程。如果你將DDL和DML語句混雜在一起,那麼數據庫會多次編譯存儲過程,這樣就降低了系統的效率。

使用註釋(Comments)你應該註釋你的T-SQL程序。這樣將來不管是誰負責這些程序,他們都會更好的理解這些程序的意義和目的。寫註釋是一個程序員因該具備的最起碼的素質。

結論

    通過這些講解,希望你對存儲過程有了一個更清晰地認識,更加明確了它是不是你當前應用開發的合適的選擇。SQL數據庫有不少有用的工具你可以好好利用,尤其是最新發布的 SQL Reporting Service。SQL數據庫的在線幫助也是一個很好的幫手,你可以在那裏找到你大部分問題的答案。

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