預存程序就是作為可執行對象存放在資料庫中的一個或多個SQL命令。
定義總是很抽象。預存程序其實就是能完成一定操作的一組SQL語句,只不過這組語句是放在資料庫中的(這裡我們只談SQL Server)。如果我們通過建立預存程序以及在ASP中調用預存程序,就可以避免將SQL語句同ASP代碼混雜在一起。這樣做的好處至少有三個:
第一、大大提高效率。預存程序本身的執行速度非常快,而且,調用預存程序可以大大減少同資料庫的互動次數。
第二、提高安全性。假如將SQL語句混合在ASP代碼中,一旦代碼失密,同時也就意味著庫結構失密。
第三、有利於SQL語句的重用。
在ASP中,一般通過command對象調用預存程序,根據不同情況,本文也介紹其它調用方法。為了方便說明,根據預存程序的輸入輸出,作以下簡單分類:
1. 只返回單一記錄集的預存程序
假設有以下預存程序(本文的目的不在於講述T-SQL文法,所以預存程序只給出代碼,不作說明):
/*SP1*/
CREATE PROCEDURE dbo.getUserList
as
set nocount on
begin
select * from dbo.[userinfo]
end
go
以上預存程序取得userinfo表中的所有記錄,返回一個記錄集。通過command對象調用該預存程序的ASP代碼如下:
'**通過Command對象調用預存程序**
DIM MyComm,MyRst
Set MyComm = Server.CreateObject("ADODB.Command")
MyComm.ActiveConnection = MyConStr 'MyConStr是資料庫連接字串
MyComm.CommandText = "getUserList" '指定預存程序名
MyComm.CommandType = 4 '表明這是一個預存程序
MyComm.Prepared = true '要求將SQL命令先行編譯
Set MyRst = MyComm.Execute
Set MyComm = Nothing
預存程序取得的記錄集賦給MyRst,接下來,可以對MyRst進行操作。
在以上代碼中,CommandType屬性工作表明請求的類型,取值及說明如下:
-1 表明CommandText參數的類型無法確定
1 表明CommandText是一般的命令類型
2 表明CommandText參數是一個存在的表名稱
4 表明CommandText參數是一個預存程序的名稱
還可以通過Connection對象或Recordset對象調用預存程序,方法分別如下:
'**通過Connection對象調用預存程序**
DIM MyConn,MyRst
Set MyConn = Server.CreateObject("ADODB.Connection")
MyConn.open MyConStr 'MyConStr是資料庫連接字串
Set MyRst = MyConn.Execute("getUserList",0,4) '最後一個參斷含義同CommandType
Set MyConn = Nothing
'**通過Recordset對象調用預存程序**
DIM MyRst
Set MyRst = Server.CreateObject("ADODB.Recordset")
MyRst.open "getUserList",MyConStr,0,1,4
'MyConStr是資料庫連接字串,最後一個參斷含義與CommandType相同
2. 沒有輸入輸出的預存程序
請看以下預存程序:
/*SP2*/
CREATE PROCEDURE dbo.delUserAll
as
set nocount on
begin
delete from dbo.[userinfo]
end
go
該預存程序刪去userinfo表中的所有記錄,沒有任何輸入及輸出,調用方法與上面講過的基本相同,只是不用取得記錄集:
'**通過Command對象調用預存程序**
DIM MyComm
Set MyComm = Server.CreateObject("ADODB.Command")
MyComm.ActiveConnection = MyConStr 'MyConStr是資料庫連接字串
MyComm.CommandText = "delUserAll" '指定預存程序名
MyComm.CommandType = 4 '表明這是一個預存程序
MyComm.Prepared = true '要求將SQL命令先行編譯
MyComm.Execute '此處不必再取得記錄集
Set MyComm = Nothing
當然也可通過Connection對象或Recordset對象調用此類預存程序,不過建立Recordset對象是為了取得記錄集,在沒有返回記錄集的情況下,還是利用Command對象吧。
3. 有傳回值的預存程序
在進行類似SP2的操作時,應充分利用SQL Server強大的交易處理功能,以維護資料的一致性。並且,我們可能需要預存程序返回執行情況,為此,將SP2修改如下:
/*SP3*/
CREATE PROCEDURE dbo.delUserAll
as
set nocount on
begin
BEGIN TRANSACTION
delete from dbo.[userinfo]
IF @@error=0
begin
COMMIT TRANSACTION
return 1
end
ELSE
begin
ROLLBACK TRANSACTION
return 0
end
return
end
go
以上預存程序,在delete順利執行時,返回1,否則返回0,並進行復原操作。為了在ASP中取得傳回值,需要利用Parameters集合來聲明參數:
'**調用帶有傳回值的預存程序並取得傳回值**
DIM MyComm,MyPara
Set MyComm = Server.CreateObject("ADODB.Command")
MyComm.ActiveConnection = MyConStr 'MyConStr是資料庫連接字串
MyComm.CommandText = "delUserAll" '指定預存程序名
MyComm.CommandType = 4 '表明這是一個預存程序
MyComm.Prepared = true '要求將SQL命令先行編譯
'聲明傳回值
Set Mypara = MyComm.CreateParameter("RETURN",2,4)
MyComm.Parameters.Append MyPara
MyComm.Execute
'取得傳回值
DIM retValue
retValue = MyComm(0) '或retValue = MyComm.Parameters(0)
Set MyComm = Nothing
在MyComm.CreateParameter("RETURN",2,4)中,各參數的含義如下:
第一個參數("RETURE")為參數名。參數名可以任意設定,但一般應與預存程序中聲明的參數名相同。此處是傳回值,我習慣上設為"RETURE";
第二個參數(2),表明該參數的資料類型,具體的類型代碼請參閱ADO參考,以下給出常用的類型代碼:
adBigInt: 20 ;
adBinary : 128 ;
adBoolean: 11 ;
adChar: 129 ;
adDBTimeStamp: 135 ;
adEmpty: 0 ;
adInteger: 3 ;
adSmallInt: 2 ;
adTinyInt: 16 ;
adVarChar: 200 ;
對於傳回值,只能取整形,且-1到-99為保留值;
第三個參數(4),表明參數的性質,此處4表明這是一個傳回值。此參數取值的說明如下:
0 : 類型無法確定; 1: 輸入參數;2: 輸入參數;3:輸入或輸出參數;4: 傳回值
以上給出的ASP代碼,應該說是完整的代碼,也即最複雜的代碼,其實
Set Mypara = MyComm.CreateParameter("RETURN",2,4)
MyComm.Parameters.Append MyPara
可以簡化為
MyComm.Parameters.Append MyComm.CreateParameter("RETURN",2,4)
甚至還可以繼續簡化,稍後會做說明。
對於帶參數的預存程序,只能使用Command對象調用(也有資料說可通過Connection對象或Recordset對象調用,但我沒有試成過)。
4. 有輸入參數和輸出參數的預存程序
傳回值其實是一種特殊的輸出參數。在大多數情況下,我們用到的是同時有輸入及輸出參數的預存程序,比如我們想取得使用者資訊表中,某ID使用者的使用者名稱,這時候,有一個輸入參數----使用者ID,和一個輸出參數----使用者名稱。實現這一功能的預存程序如下:
/*SP4*/
CREATE PROCEDURE dbo.getUserName
@UserID int,
@UserName varchar(40) output
as
set nocount on
begin
if @UserID is null return
select @UserName=username
from dbo.[userinfo]
{
DiggIt(507302,19247,1)
}">0 {
DiggIt(507302,19247,1)
}">
一、適合讀者對象:資料庫開發程式員,資料庫的資料量很多,涉及到對SP(預存程序)的最佳化的項目開發人員,對資料庫有濃厚興趣的人。
二、介紹:在資料庫的開發過程中,經常會遇到複雜的商務邏輯和對資料庫的操作,這個時候就會用SP來封裝資料庫操作。如果項目的SP較多,書寫又沒有一定的規範,將會影響以後的系統維護困難和大SP邏輯的難以理解,另外如果資料庫的資料量大或者項目對SP的效能要求很,就會遇到最佳化的問題,否則速度有可能很慢,經過親身經驗,一個經過最佳化過的SP要比一個效能差的SP的效率甚至高几百倍。
三、內容:
1、開發人員如果用到其他庫的Table或View,務必在當前庫中建立View來實現跨庫操作,最好不要直接使用“databse.dbo.table_name”,因為sp_depends不能顯示出該SP所使用的跨庫table或view,不方便校正。
2、開發人員在提交SP前,必須已經使用set showplan on分析過查詢計劃,做過自身的查詢最佳化檢查。
3、高程式運行效率,最佳化應用程式,在SP編寫過程中應該注意以下幾點:
a)SQL的使用規範:
i. 盡量避免大事務操作,慎用holdlock子句,提高系統並發能力。
ii. 盡量避免反覆訪問同一張或幾張表,尤其是資料量較大的表,可以考慮先根據條件提取資料到暫存資料表中,然後再做串連。
iii. 盡量避免使用遊標,因為遊標的效率較差,如果遊標操作的資料超過1萬行,那麼就應該改寫;如果使用了遊標,就要盡量避免在遊標迴圈中再進行表串連的操作。
iv. 注意where字句寫法,必須考慮語句順序,應該根據索引順序、範圍大小來確定條件子句的前後順序,儘可能的讓欄位順序與索引順序相一致,範圍從大到小。
v. 不要在where子句中的“=”左邊進行函數、算術運算或其他運算式運算,否則系統將可能無法正確使用索引。
vi. 盡量使用exists代替select count(1)來判斷是否存在記錄,count函數只有在統計表中所有行數時使用,而且count(1)比count(*)更有效率。
vii. 盡量使用“>=”,不要使用“>”。
viii. 注意一些or子句和union子句之間的替換
ix. 注意表之間串連的資料類型,避免不同類型資料之間的串連。
x. 注意預存程序中參數和資料類型的關係。
xi. 注意insert、update操作的資料量,防止與其他應用衝突。如果資料量超過200個資料頁面(400k),那麼系統將會進行鎖定擴大,頁級鎖會升級成表級鎖。
b)索引的使用規範:
i. 索引的建立要與應用結合考慮,建議大的OLTP表不要超過6個索引。
ii. 儘可能的使用索引欄位作為查詢條件,尤其是聚簇索引,必要時可以通過index index_name來強制指定索引
iii. 避免對大表查詢時進行table scan,必要時考慮建立索引。
iv. 在使用索引欄位作為條件時,如果該索引是聯合索引,那麼必須使用到該索引中的第一個欄位作為條件時才能保證系統使用該索引,否則該索引將不會被使用。
v. 要注意索引的維護,周期性重建索引,重新編譯預存程序。
c)tempdb的使用規範:
i. 盡量避免使用distinct、order by、group by、having、join、cumpute,因為這些語句會加重tempdb的負擔。
ii. 避免頻繁建立和刪除暫存資料表,減少系統資料表資源的消耗。
iii. 在建立暫存資料表時,如果一次性插入資料量很大,那麼可以使用select into代替create table,避免log,提高速度;如果資料量不大,為了緩和系統資料表的資源,建議先create table,然後insert。
iv. 如果暫存資料表的資料量較大,需要建立索引,那麼應該將建立暫存資料表和建立索引的過程放在單獨一個子預存程序中,這樣才能保證系統能夠很好的使用到該暫存資料表的索引。
v. 如果使用到了暫存資料表,在預存程序的最後務必將所有的暫存資料表顯式刪除,先truncate table,然後drop table,這樣可以避免系統資料表的較長時間鎖定。
vi. 慎用大的暫存資料表與其他大表的串連查詢和修改,減低系統資料表負擔,因為這種操作會在一條語句中多次使用tempdb的系統資料表。
d)合理的演算法使用:
根據上面已提到的SQL最佳化技術和ASE Tuning手冊中的SQL最佳化內容,結合實際應用,採用多種演算法進行比較,以獲得消耗資源最少、效率最高的方法。具體可用ASE調優命令:set statistics io on, set statistics time on , set showplan on 等。