|
5. 返回值 對(duì)函數(shù)返回值的處理不同于存儲(chǔ)過(guò)程返回值的處理,這常常導(dǎo)致混淆。在函數(shù)中,經(jīng)常是返回一個(gè)布爾值來(lái)表明函數(shù)運(yùn)行的成功與否。 If SomeFunctionName() = True Then ' Function succeeded 但在調(diào)用一個(gè)存儲(chǔ)過(guò)程時(shí),卻不能使用同樣的方法,因?yàn)榇鎯?chǔ)是用Execute方法運(yùn)行的,同時(shí)返回一個(gè)記錄集。 Set rsAuthors = cmdAuthors.Execute 如果得不到一個(gè)返回值,如何確定是否已正確執(zhí)行存儲(chǔ)過(guò)程?當(dāng)發(fā)生錯(cuò)誤時(shí),會(huì)報(bào)告錯(cuò)誤,這樣就可使用前一章提供的錯(cuò)誤處理代碼來(lái)處理錯(cuò)誤。但對(duì)于一些非致命的邏輯錯(cuò)誤怎么辦? 例如,考慮向employee表添加一個(gè)新職員的情形。你可能不想防止兩個(gè)職員同名的情況,但想注明這個(gè)情況。那么,可以使用一個(gè)返回值以表明是否已有同名的職員存在。存儲(chǔ)過(guò)程如下: CREATE PROCEDURE usp_AddEmployee @Emp_ID Char(9), @FName Varchar(20), @Minit Char(1), @LName Varchar(30), @Job_ID SmallInt, @Job_Lvl TinyInt, @Pub_ID Char(4), @Hire_Date Datetime AS BEGIN DECLARE @Exists Int -- Return value
-- See if an employee with the same name exists IF EXISTS(SELECT * FROM Employee WHERE FName = @FName AND MInit = @MInit AND LName = @LName) SELECT @Exists = 1 ELSE SELECT @Exists = 0
INSERT INTO Employee (emp_id, fname, minit, lname, job_id, job_lvl, pub_id, hire_date) VALUES (@Emp_Id, @FName, @MInit, @LName, @Job_ID, @Job_Lvl, @Pub_ID, @Hire_Date) RETURN @Exists END 該過(guò)程首先檢查是否有同名的職員存在,并據(jù)此設(shè)定相應(yīng)的變量Exists,若存在同名,就設(shè)為1,否則為0。然后將該職員加到表中,同時(shí)把Exists的值作為返回值返回。 注意盡管返回了一個(gè)值,但并未將其聲明為存儲(chǔ)過(guò)程的參數(shù)。 調(diào)用該過(guò)程的ASP代碼如下: <!-- #INCLUDE FILE="../include/Connection.asp" --> <% Dim cmdEmployee Dim lngRecs Dim lngAdded
Set cmdEmployee = Server.CreateObject("ADODB.Command")
' Set the properties of the command With cmdEmployee .ActiveConnection = strConn .CommandText = "usp_AddEmployee" .CommandType = adCmdStoredProc
' Create the parameters ' Notice that the return value is the first parameter .Parameters.Append .CreateParameter ("RETURN_VALUE", adInteger, _ adParamReturnValue) .Parameters.Append .CreateParameter ("@Emp_id", adChar, adParamInput, 9) .Parameters.Append .CreateParameter ("@fname", adVarWChar, adParamInput, 20) .Parameters.Append .CreateParameter ("@minit", adChar, adParamInput, 1) .Parameters.Append .CreateParameter ("@lname", adVarWChar, adParamInput, 30) .Parameters.Append .CreateParameter ("@job_id", adSmallInt, adParamInput) .Parameters.Append .CreateParameter ("@job_lvl", adUnsignedTinyInt, adParamInput) .Parameters.Append .CreateParameter ("@pub_id", adChar, adParamInput, 4) .Parameters.Append .CreateParameter ("@hire_date", adDBTimeStamp, _ adParamInput, 8)
' Set the parameter values .Parameters("@Emp_id") = Request.Form("txtEmpID") .Parameters("@fname") = Request.Form("txtFirstName") .Parameters("@minit") = Request.Form("txtInitial") .Parameters("@lname") = Request.Form("txtLastName") .Parameters("@job_id") = Request.Form("lstJobs") .Parameters("@job_lvl") = Request.Form("txtJobLevel") .Parameters("@pub_id") = Request.Form("lstPublisher") .Parameters("@hire_date") = Request.Form("txtHireDate")
' Run the stored procedure .Execute lngRecs, , adExecuteNoRecords
' Extract the return value lngAdded = .Parameters("RETURN_VALUE") End With
Response.Write "New employee added.<P>" If lngAdded = 1 Then Response.Write "An employee with the same name already exists." End If
Set cmdEmployee = Nothing %> 需要重點(diǎn)注意,返回值應(yīng)當(dāng)作為集合中第一個(gè)參數(shù)被創(chuàng)建。即使返回值并不作為一個(gè)參數(shù)出現(xiàn)在存儲(chǔ)過(guò)程中,總是Parameters集合中的第一個(gè)Parameters。 因此,特別強(qiáng)調(diào)一點(diǎn): 存儲(chǔ)過(guò)程的返回值必須聲明為Parameters集合中第一個(gè)參數(shù),同時(shí)參數(shù)的Direction值必須為adParamReturnValue。 使用返回值 現(xiàn)在定義一個(gè)初始窗體,如圖9-3所示:
圖9-3 初始窗體界面 按下Add Employee按鈕會(huì)產(chǎn)生如圖9-4所示的顯示:
圖9-4 按下Add Employee按鈕后顯示的界面 再添加同樣的細(xì)節(jié)(ID號(hào)不同)會(huì)得到如圖9-5所示的界面:
圖9-5 添加細(xì)節(jié)后顯示的界面 6. 更新參數(shù) 無(wú)需輸入所有的參數(shù)細(xì)節(jié),只需調(diào)用Refresh方法,就能讓ADO完成更新。例如,假設(shè)已經(jīng)創(chuàng)建了一個(gè)帶有與前面例子相同的參數(shù)的過(guò)程usp_AddEmployee,并且沒(méi)有改變運(yùn)行的頁(yè)面。 With cmdEmployee .ActiveConnection = strConn .CommandText = "usp_Addemployee" .CommandType = adCmdStoredProc 然后調(diào)用Refresh方法。 .Parameters.Refresh 這告訴ADO向數(shù)據(jù)存儲(chǔ)請(qǐng)求每個(gè)參數(shù)的細(xì)節(jié),并創(chuàng)建Parameters集合。然后可以為其賦值。 .Parameters("@Emp_Id") = Request.Form("txtEmpID") .Parameters("@FName") = Request.Form("txtFirstName") .Parameters("@MInit") = Request.Form("txtInitial") .Parameters("@LName") = Request.Form("txtLastName") .Parameters("@Job_ID") = Request.Form("lstJobs") .Parameters("@Job_Lvl") = Request.Form("txtJobLevel") .Parameters("@Pub_ID") = Request.Form("lstPublisher") .Parameters("@Hire_Date") = Request.Form("txtHireDate") 注意并不需要?jiǎng)?chuàng)建任何參數(shù),包括返回值。 這似乎真是一條捷徑,但應(yīng)意識(shí)到這種方法也造成了性能上的損失,因?yàn)锳DO必須向提供者查詢以獲得存儲(chǔ)過(guò)程的參數(shù)細(xì)節(jié)。盡管如此,這種方法還是很有用的,尤其是在從參數(shù)中取出正確的值有困難的時(shí)候。 實(shí)際上,可以編寫一個(gè)小實(shí)用程序作為開(kāi)發(fā)工具使用,用來(lái)完成更新并建立Append語(yǔ)句,可以將其粘貼到自己的代碼中。它看上去應(yīng)該與圖9-6所示的GenerateParameters.asp ASP頁(yè)面類似。
圖9-6 GenerateParameters.asp ASP頁(yè)面 其代碼相當(dāng)簡(jiǎn)單。首先是包含連接符串和另一個(gè)ADOX常數(shù)文件。 <!-- #INCLUDE FILE="../Include/Connection.asp" --> <!-- #INCLUDE FILE="../Include/ADOX.asp" --> 接下來(lái)創(chuàng)建一個(gè)窗體,指定目標(biāo)為PrintParameters.asp ASP頁(yè)面。 <FORM NAME="Procedures" METHOD="post" ACTION="PrintParameters.asp"> Connection String:<BR> <TEXTAREA NAME="txtConnection" COLS="80" ROWS="5"> <% = strConn %> </TEXTAREA> <P> Stored Procedure:<BR> <SELECT NAME="lstProcedures"> 然后,使用ADOX從SQL Server中得到存儲(chǔ)過(guò)程的列表,同時(shí)創(chuàng)建一個(gè)含有這些存儲(chǔ)過(guò)程名字的列表框。 <% Dim catPubs Dim procProcedure
' Predefine the quote character strQuote = Chr(34) Set catPubs = Server.CreateObject("ADOX.Catalog")
catPubs.ActiveConnection = strConn
For Each procProcedure In catPubs.Procedures Response.Write "<OPTION VALUE=" & _ strQuote & procProcedure.Name & _ strQuote & ">" & procProcedure.Name Next
Set procProcedure = Nothing Set catPubs = Nothing %> </SELECT> <P> <INPUT TYPE="submit" VALUE="Print Paramaters"> </FORM> 這是一個(gè)簡(jiǎn)單的窗體,包括一個(gè)用于顯示連接字符串的TEXTAREA控件和用于顯示存儲(chǔ)過(guò)程名稱的SELECT控件。以前沒(méi)有見(jiàn)過(guò)的是ADOX,ADOX是數(shù)據(jù)定義與安全的ADO擴(kuò)展,可以用來(lái)訪問(wèn)數(shù)據(jù)存儲(chǔ)的目錄(或是元數(shù)據(jù))。 本書不打算介紹ADOX的內(nèi)容,但其十分簡(jiǎn)單。進(jìn)一步的細(xì)節(jié)可參見(jiàn)《ADO Programmer's Reference》,Wrox出版社出版,2.1版或2.5版都行。 上面的例子使用了Procedures集合,這個(gè)集合包含數(shù)據(jù)存儲(chǔ)中的所有存儲(chǔ)過(guò)程的列表。按下PrintParameters按鈕時(shí),將得到圖9-7所示的顯示:
圖9-7 按下Print Parameters按鈕時(shí)顯示的界面 可以簡(jiǎn)單地從這里拷貝參數(shù)行到代碼中。在前面使用了一個(gè)以前從未見(jiàn)過(guò)的包含文件。該文件包含了幾個(gè)將ADO常數(shù)(例如數(shù)據(jù)類型、參數(shù)方向等)轉(zhuǎn)換為字符串值的函數(shù): <!-- #INCLUDE FILE="../Include/Descriptions.asp" --> 接下來(lái),定義一些變量,提取用戶請(qǐng)求并創(chuàng)建Command對(duì)象。 <% Dim cmdProc Dim parP Dim strConnection Dim strProcedure Dim strQuote
' Get the connection and procedure name from the user strQuote = Chr(34) strConnection = Request.Form("txtConnection") strProcedure = Request.Form("lstProcedures")
'Update the user Response.Write "Connecting to <B>" & strConnection & "</B><BR>" Response.Write "Documenting parameters for <B>" & _ strProcedure & "</B><P><P>"
Set cmdProc = Server.CreateObject("ADODB.Command")
' Set the properties of the command, using the name ' of the procedure that the user selected With cmdProc .ActiveConnection = strConnection .CommandType = adCmdStoredProc .CommandText = strProcedure 然后使用Refresh方法自動(dòng)填寫Parameters集合。 .Parameters.Refresh 現(xiàn)在可以遍歷整個(gè)集合,寫出包含創(chuàng)建參數(shù)所需的細(xì)節(jié)內(nèi)容的字符串。 For Each parP In .Parameters Response.Write ".Parameters.Append & _ "("strQuote & parP.Name & _ strQuote & ", " & _ DataTypeDesc(parP.Type) & ", " & _ ParamDirectionDesc(parP.Direction) & _ ", " & _ parP.Size & ")<BR>" Next End With
Set cmdProc = Nothing %> 在Descriptions.asp包含文件中可以找到函數(shù)DataTypeDesc和ParamDirectionDesc。 Descriptions.asp包含文件以及其他的例子文件可以在Web站點(diǎn)http://www.wrox.com中找到。 這是一個(gè)非常簡(jiǎn)單的技術(shù),它較好地使用了Refresh方法。
9.3 優(yōu)化 優(yōu)化是每個(gè)開(kāi)發(fā)人員應(yīng)該關(guān)心的問(wèn)題。對(duì)于數(shù)據(jù)庫(kù)訪問(wèn),優(yōu)化是一個(gè)關(guān)鍵問(wèn)題。和其他任務(wù)相比,數(shù)據(jù)的訪問(wèn)顯得相對(duì)慢些。 因?yàn)閿?shù)據(jù)訪問(wèn)的變化是如此之多,以致于幾乎不可能提出一套固定的數(shù)據(jù)庫(kù)操作的優(yōu)化規(guī)則。通常碰到這類問(wèn)題,經(jīng)常得到這樣的回答:“這取決于……”,因?yàn)檫@類優(yōu)化問(wèn)題取決于準(zhǔn)備做什么。
9.3.1 常用的ADO技巧 盡管優(yōu)化取決于所執(zhí)行的任務(wù),但是仍然有一些常用的技巧: · 僅選擇所需的列。當(dāng)打開(kāi)記錄集時(shí),不要自動(dòng)地使用表名(即SELECT *),除非需要獲得所有的列。使用單獨(dú)的列意味著將減少發(fā)送到服務(wù)器或從服務(wù)器取出的數(shù)據(jù)的數(shù)量。即使需要使用全部列,單獨(dú)地命名每個(gè)列也會(huì)獲得最佳的性能,因?yàn)榉⻊?wù)器不必再解釋這些列是什么名字。 · 盡可能使用存儲(chǔ)過(guò)程。存儲(chǔ)過(guò)程是預(yù)先編譯的程序,含有一個(gè)已經(jīng)準(zhǔn)備好的執(zhí)行計(jì)劃,所以比SQL語(yǔ)句執(zhí)行得更快。 · 使用存儲(chǔ)過(guò)程更改數(shù)據(jù)。這總是比在記錄集上使用ADO方法執(zhí)行速度快。 · 除非必需否則不要?jiǎng)?chuàng)建記錄集。運(yùn)行操作查詢時(shí),要確定加入了adExecuteNoRecords選項(xiàng),這樣記錄集就不會(huì)創(chuàng)建。當(dāng)僅僅返回一個(gè)或兩個(gè)字段的單行記錄時(shí)(比如ID值),也可以在查詢狀態(tài)下使用這種方法。在這種情況下,存儲(chǔ)過(guò)程和輸出參數(shù)將會(huì)更快。 · 使用適當(dāng)?shù)墓鈽?biāo)和鎖定模式。如果所做的全部工作是從記錄集中讀取數(shù)據(jù),并將其顯示在屏幕上(比如,創(chuàng)建一個(gè)表),那么使用缺省的只能前移的、只讀的記錄集。ADO用來(lái)維護(hù)記錄和鎖定細(xì)節(jié)的工作越少,執(zhí)行的性能就越高。
9.3.2 對(duì)象變量 當(dāng)遍歷記錄集時(shí),一個(gè)保證能提高性能的方法是使用對(duì)象變量指向集合中的成員。例如,考慮下面的遍歷含有Authors表的記錄集的例子。 While Not rsAuthors.EOF Response.Write rsAuthors("au_fname") & " " & _ rsAuthors("au_lname") & "<BR>" rsAuthors.MoveNext Wend 可以用下面的方法加速代碼執(zhí)行,同時(shí)使其更易于理解。 Set FirstName = rsAuthors("au_fname") Set LastName = rsAuthors("au_lname")
While Not rsAuthors.EOF Response.Write FirstName & " " & LastName & "<BR>" rsAuthors.MoveNext Wend 這里使用了兩個(gè)變量,并指向記錄集的Fidds集合中的特定字段(記住,F(xiàn)idds集合是缺省的集合)。因?yàn)檫@里建立了一個(gè)對(duì)象的引用,所以可以使用對(duì)象變量而不是實(shí)際的變量,這意味著腳本引擎的工作減少了,因?yàn)樵诩现羞M(jìn)行索引的次數(shù)變少了。
9.3.3 高速緩存大小 高速緩存的大小是指ADO每次從數(shù)據(jù)存儲(chǔ)中讀取的記錄的數(shù)量,缺省為1。這意味著當(dāng)使用基于服務(wù)器的光標(biāo)時(shí),每當(dāng)移動(dòng)到另一條記錄時(shí),必須從數(shù)據(jù)存儲(chǔ)中提取記錄。舉一個(gè)例子,如果增大高速緩存的大小為10,那么每次讀ADO緩沖區(qū)的記錄數(shù)將變?yōu)?0。如果訪問(wèn)位于高速緩存內(nèi)的記錄,那么ADO不需要從數(shù)據(jù)存儲(chǔ)中取記錄。當(dāng)訪問(wèn)位于高速緩存外的記錄時(shí)則下一批記錄將讀入到高速緩存中。 通過(guò)使用記錄集的CacheSize屬性,可以設(shè)置高速緩存的大小。 rsAuthors.CacheSize = 10 可以在記錄集生命期的任何時(shí)候改變高速緩存的大小,但新的數(shù)量只在提取下一批記錄后才有效。 與許多改進(jìn)性能的技巧類似,高速緩存沒(méi)有通用的最佳大小,因?yàn)樗S任務(wù)、數(shù)據(jù)和提供者的不同而改變。但是,從1開(kāi)始增加高速緩存的大小總是能提高性能。 如果你想看到這一點(diǎn),可以使用SQL Server profiler并查看使用缺省的高速緩存打開(kāi)一個(gè)記錄集發(fā)生的情況,并比較增大高速緩存后發(fā)生的情況。增大高速緩存的大小不僅減低了ADO的工作量,同時(shí)也降低了SQL Server的工作量。
9.3.4 數(shù)據(jù)庫(kù)設(shè)計(jì) 不要希望只通過(guò)編程來(lái)提高對(duì)數(shù)據(jù)的訪問(wèn)效率,應(yīng)該同時(shí)考慮一下數(shù)據(jù)庫(kù)的設(shè)計(jì)。這里并不打算對(duì)數(shù)據(jù)庫(kù)設(shè)計(jì)進(jìn)行更多的討論,但在使用Web站點(diǎn)數(shù)據(jù)庫(kù)時(shí)應(yīng)考慮以下幾點(diǎn): · 實(shí)時(shí)數(shù)據(jù):向用戶顯示數(shù)據(jù)時(shí),確保數(shù)據(jù)內(nèi)容總是最新是十分重要的。以一份產(chǎn)品目錄為例,目錄內(nèi)容改變的頻率有多快?如果該目錄并非經(jīng)常改變,那么不必每次都從數(shù)據(jù)庫(kù)中提取數(shù)據(jù)。每周一次,或在數(shù)據(jù)改變時(shí)從數(shù)據(jù)庫(kù)產(chǎn)生一個(gè)靜態(tài)的HTML頁(yè)面應(yīng)是一個(gè)更好的辦法。 · 索引:如果需要對(duì)表進(jìn)行大量的查詢,而不執(zhí)行太多的添加數(shù)據(jù)操作,那么可以考慮為表建立索引。 · 不規(guī)范化:如果站點(diǎn)有兩個(gè)不同的目的(數(shù)據(jù)維護(hù)與數(shù)據(jù)分析),那么可以考慮采用一些不規(guī)范化的表以便有助于數(shù)據(jù)的分析?梢蕴峁┆(dú)立的、完全不規(guī)范化的但能正常更新的分析用表,為了改善性能甚至可以將這些分析表移到另一臺(tái)機(jī)器上。 · 數(shù)據(jù)庫(kù)統(tǒng)計(jì):如果使用的是SQL Server 6.x,如果數(shù)據(jù)被添加或刪附除,那么應(yīng)定期更新統(tǒng)計(jì)結(jié)果。這些統(tǒng)計(jì)結(jié)果用于產(chǎn)生一個(gè)查詢計(jì)劃,會(huì)影響查詢的運(yùn)行。請(qǐng)閱讀SQL Books Online中的UPDATE STATISTIC以便了解更詳細(xì)的內(nèi)容。在SQL Server 7.0中這一任務(wù)自動(dòng)完成。 這些都是十分基本的數(shù)據(jù)庫(kù)設(shè)計(jì)技巧,但若只埋頭于ASP代碼可能不會(huì)考慮到這些。
|