在服務(wù)器上生成動態(tài)內(nèi)容是使用ASP最主要的原因之一,所以我們選擇的第一個測試項目是確定把動態(tài)內(nèi)容發(fā)送到應(yīng)答流使用什么方法最好。基本的選擇有兩種(以及它們的一些變化):使用內(nèi)嵌ASP標(biāo)記,使用Response.Write語句。
為測試這些不同的方法,我們創(chuàng)建了一個簡單的ASP頁面,頁面先定義一些變量然后把它們插入到表格。雖然這個頁面很簡單,而且沒有實際用途,但它足以讓我們分離和測試各個問題。
2.1 使用ASP內(nèi)嵌標(biāo)記
第一個測試是使用ASP的內(nèi)嵌標(biāo)記%= x %>,其中x是一個變量。這是使用最方便的方法,而且它可以讓頁面的HTML部分變得更容易閱讀和維護。
復(fù)制代碼 代碼如下:
% OPTION EXPLICIT
Dim FirstName
Dim LastName
Dim MiddleInitial
Dim Address
Dim City
Dim State
Dim PhoneNumber
Dim FaxNumber
Dim EMail
Dim BirthDate
FirstName = "John"
MiddleInitial = "Q"
LastName = "Public"
Address = "100 Main Street"
City = "New York"
State = "NY"
PhoneNumber = "1-212-555-1234"
FaxNumber = "1-212-555-1234"
EMail = "john@public.com"
BirthDate = "1/1/1950"
%>
HTML>
HEAD>
TITLE>Response Test/ TITLE>
/HEAD>
BODY>
H1>Response Test/H1>
TABLE>
tr>td>b>First Name:/b>/td>td>%= FirstName %>/td>/tr>
tr>td>b>Middle Initial:/b>/td>td>%= MiddleInitial %>/td>/tr>
tr>td>b>Last Name:/b>/td>td>%= LastName %>/td>/tr>
tr>td>b>Address:/b>/td>td>%= Address %>/td>/tr>
tr>td>b>City:/b>/td>td>%= City %>/td>/tr>
tr>td>b>State:/b>/td>td>%= State %>/td>/tr>
tr>td>b>Phone Number:/b>/td>td>%= PhoneNumber %>/td>/tr>
tr>td>b>Fax Number:/b>/td>td>%= FaxNumber %>/td>/tr>
tr>td>b>EMail:/b>/td>td>%= EMail %>/td>/tr>
tr>td>b>Birth Date:/b>/td>td>%= BirthDate %>/td>/tr>
/TABLE>
/BODY>
/HTML>
/app1/response1.asp的完整代碼
最好記錄 = 8.28 毫秒/頁
2.2 使用Response.Write輸出每一行HTML代碼
許多優(yōu)秀的文獻(xiàn)指出,應(yīng)當(dāng)避免使用前面的內(nèi)嵌標(biāo)記方法,因為它導(dǎo)致一個稱為“上下文切換”的操作。這個操作發(fā)生在Web服務(wù)器所處理的代碼類型發(fā)生變化的時候(從純HTML的發(fā)送到腳本處理,或反過來),這種切換需要一定的時間。許多程序員在了解了這一點之后,他們的第一個反應(yīng)是將每一行HTML代碼都用Response.Write函數(shù)來輸出:
復(fù)制代碼 代碼如下:
...
Response.Write("html>")
Response.Write("head>")
Response.Write(" title>Response Test/title>")
Response.Write("/head>")
Response.Write("body>")
Response.Write("h1>Response Test/h1>")
Response.Write("table>")
Response.Write("tr>td>b>First Name:/b>/td>td>" FirstName "/td>/tr>")
Response.Write("tr>td>b>Middle Initial:/b>/td>td>" MiddleInitial "/td>/tr>")
...
/app1/response2.asp片斷
最好記錄 = 8.28 毫秒/頁
響應(yīng)時間 = 8.08 毫秒/頁
差 額 = -0.20 毫秒 (減少 2.4%)
和內(nèi)嵌標(biāo)記版本相比,我們所看到的性能改善非常小,簡直令人驚訝。這或許是因為頁面中多出了許多函數(shù)調(diào)用。不過這種方法還有一個更大的缺點,由于HTML代碼嵌入到了函數(shù)內(nèi),腳本代碼變得非常冗長,閱讀和維護都不方便。
2.3 使用封裝函數(shù)
Response.Write并不會在文本行的末尾加上CRLF(Carriage Return - Line Feed,回車換行),這是使用上面這種方法最令人失望的地方。盡管已經(jīng)在服務(wù)器端把HTML代碼作了很好的格式化,但在瀏覽器中看到的仍舊只有長長的一行代碼。不過失望的不僅是這一個問題,人們很快就發(fā)現(xiàn)不存在能夠自動添加CRLF的Response.WriteLn函數(shù)。一個很自然的反應(yīng)就是創(chuàng)建Response.Write的封裝函數(shù),在每行的后面加上CRLF:
...
writeCR("tr>td>b>First Name:/b>/td>td>" FirstName "/td>/tr>")
...
SUB writeCR(str)
Response.Write(str vbCRLF)
END SUB
/app1/response4.asp片斷
最好記錄 = 8.08 毫秒/頁
響應(yīng)時間 = 10.11 毫秒/頁
差 額 = +2.03 毫秒 (增加 25.1%)
結(jié)果是性能的大大下降。當(dāng)然,這主要是因為這種方法使得函數(shù)的調(diào)用次數(shù)加倍,它對性能的影響非常明顯。應(yīng)當(dāng)不惜代價地避免這種用法,CRLF導(dǎo)致每行的末尾多了兩個字節(jié),而這兩個字節(jié)對于瀏覽器顯示頁面是沒有用的。在大多數(shù)情況下,瀏覽器端HTML代碼的格式美觀只是方便了你的競爭者閱讀和理解頁面的設(shè)計。
2.4 合并多個Response.Write
如果不考慮最后一次有關(guān)封裝函數(shù)的測試,下一個合理的步驟應(yīng)當(dāng)是將所有字符串從分開的Response.Write語句合并到一個語句,從而減少函數(shù)調(diào)用次數(shù)、提高代碼運行效率。
復(fù)制代碼 代碼如下:
Response.Write("html>" _
"head>" _
"title>Response Test/title>" _
"/head>" _
"body>" _
"h1>Response Test/h1>" _
"table>" _
"tr>td>b>First Name:/b>/td>td>" FirstName "/td>/tr>" _
...
"tr>td>b>Birth Date:/b>/td>td>" BirthDate "/td>/tr>" _
"/table>" _
"/body>" _
"/html>")
/app1/response3.asp片斷
最好記錄 = 8.08 毫秒/頁
響應(yīng)時間 = 7.05 毫秒/頁
差 額 = -1.03 毫秒 (減少 12.7%)
這是目前為止最好的方法。
2.5 合并多個Response.Write,且在每一行的末尾增加CRLF
也有人非常關(guān)注他們的HTML代碼在瀏覽器端是否美觀,因此我們又在每一行HTML代碼的末尾加上了一個回車,使用的是vbCRLF常量,其他的測試代碼與上例一樣。
...
Response.Write("html>" vbCRLF _
"head>" vbCRLF _
" title>Response Test/title>" vbCRLF _
"/head>" vbCRLF _
...
/app1/response5.asp片斷
最好記錄 = 7.05 毫秒/頁
響應(yīng)時間 = 7.63 毫秒/頁
差 額 = +0.58 毫秒 (增加 8.5%)
結(jié)果是性能略有下降,這可能是因為增加了字符串連接操作,同時輸出的文本也增加了。
2.6 意見
根據(jù)上述ASP輸出測試的結(jié)果,我們得出如下編碼規(guī)則:
避免過多地使用內(nèi)嵌ASP。
把盡可能多的Response.Write語句合并成單個語句。
絕對不要為了加上CRLF而封裝Response.Write。
如果要格式化HTML輸出,直接在Response.Write語句后面加上CRLF。
綱要:ASP動態(tài)生成的內(nèi)容以什么方式輸出效率最高?最好用哪種方法提取數(shù)據(jù)庫記錄集?本文測試了近20個這類ASP開發(fā)中常見的問題,測試工具所顯示的時間告訴我們:這些通??梢韵氘?dāng)然的問題不僅值得關(guān)注,而且還有出乎意料的秘密隱藏在內(nèi)。
一、測試目的
本文的第一部分考察了ASP開發(fā)中的一些基本問題,給出了一些性能測試結(jié)果以幫助讀者理解放入頁面的代碼到底對性能有什么影響。ADO是由Microsoft開發(fā)的一個通用、易用的數(shù)據(jù)庫接口,事實證明通過ADO與數(shù)據(jù)庫交互是ASP最重要的應(yīng)用之一,在第二部分中,我們就來研究這個問題。
ADO所提供的功能相當(dāng)廣泛,因此準(zhǔn)備本文最大的困難在于如何界定問題的范圍??紤]到提取大量的數(shù)據(jù)可能顯著地增加Web服務(wù)器的負(fù)載,所以我們決定這一部分的主要目的是找出什么才是操作ADO記錄集的最優(yōu)配置。然而,即使縮小了問題的范圍,我們?nèi)耘f面臨很大的困難,因為ADO可以有許多種不同的方法來完成同一個任務(wù)。例如,記錄集不僅可以通過Recordset類提取,而且也可以通過Connection和Command類提取;即使得到記錄集對象之后,還有許多可能戲劇性地影響性能的操作方法。然而,與第一部分一樣,我們將盡可能地涵蓋最廣泛的問題。
具體地講,這一部分的目標(biāo)是收集足夠多的信息,回答下列問題:
l是否應(yīng)該通過包含引用ADOVBS.inc?
l使用記錄集時是否應(yīng)該創(chuàng)建單獨的連接對象?
l最好用哪種方法提取記錄集?
l哪種游標(biāo)類型和記錄鎖定方式效率最高?
l是否應(yīng)該使用本地記錄集?
l設(shè)置記錄集屬性用哪種方法最好?
l用哪種方法引用記錄集字段值效率最高?
l用臨時字符串收集輸出是一種好方法嗎?
二、測試環(huán)境
本測試總共用到了21個ASP文件,這些文件可以從本文后面下載。每一個頁面設(shè)置成可以運行三種不同的查詢,分別返回0、25、250個記錄。這將幫助我們隔離頁面本身的初始化、運行開銷與用循環(huán)訪問記錄集的開銷。
為便于測試,數(shù)據(jù)庫連接字符串和SQL命令串都在Global.asa中作為Application變量保存。由于我們的測試數(shù)據(jù)庫是SQL Server 7.0,因此連接串指定OLEDB作為連接提供者,測試數(shù)據(jù)來自SQL Server的Northwind數(shù)據(jù)庫。SQL SELECT命令從NorthWind Orders表提取7個指定的字段。
復(fù)制代碼 代碼如下:
SCRIPT LANGUAGE=VBScript RUNAT=Server>
Sub Application_OnStart
Application("Conn") = "Provider=SQLOLEDB; " _
"Server=MyServer; " _
"uid=sa; " _
"pwd=;" _
"DATABASE=northwind"
Application("SQL") = "SELECTTOP 0OrderID, " _
"CustomerID, " _
"EmployeeID, " _
"OrderDate, " _
"RequiredDate, " _
"ShippedDate, " _
"Freight " _
"FROM[Orders] "
End Sub
/SCRIPT>
'alternate sql - 25 records
Application("SQL") = "SELECTTOP 25OrderID, " _
"CustomerID, " _
"EmployeeID, " _
"OrderDate, " _
"RequiredDate, " _
"ShippedDate, " _
"Freight " _
"FROM[Orders] "
'alternate sql - 250 records
Application("SQL") = "SELECTTOP 250 OrderID, " _
"CustomerID, " _
"EmployeeID, " _
"OrderDate, " _
"RequiredDate, " _
"ShippedDate, " _
"Freight " _
"FROM[Orders] "
測試服務(wù)器配置如下:450 Mhz Pentium,512 MB RAM,NT Server 4.0 SP5,MDAC 2.1(數(shù)據(jù)訪問組件),以及5.0版本的Microsoft腳本引擎。SQL Server運行在另外一臺具有類似配置的機器上。和第一部分一樣,我們?nèi)耘f使用Microsoft Web Application Stress Tool 記錄從第一個頁面請求到從服務(wù)器接收到最后一個字節(jié)的時間(TTLB,Time To Last Byte),時間以毫秒為單位。測試腳本調(diào)用每個頁面1300次以上,運行時間約20小時,以下顯示的時間是會話的平均TTLB。請記住,和第一部分一樣,我們只關(guān)心代碼的效率,而不是它的可伸縮性或服務(wù)器性能。
同時請注意我們啟用了服務(wù)器的緩沖。另外,為了讓所有的文件名字長度相同,有的文件名字中嵌入了一個或多個下劃線。
三、第一次測試
在第一次測試中,我們模擬Microsoft ASP ADO示例中可找到的典型情形提取一個記錄集。在這個例子(ADO__01.asp)中,我們首先打開一個連接,然后創(chuàng)建記錄集對象。當(dāng)然,這里的腳本按照本文第一部分所總結(jié)的編碼規(guī)則作了優(yōu)化。
復(fù)制代碼 代碼如下:
% Option Explicit %>
!-- #Include file="ADOVBS.INC" -->
%
Dim objConn
Dim objRS
Response.Write( _
"HTML>HEAD>" _
"TITLE>ADO Test/TITLE>" _
"/HEAD>BODY>" _
)
Set objConn = Server.CreateObject("ADODB.Connection")
objConn.Open Application("Conn")
Set objRS = Server.CreateObject("ADODB.Recordset")
objRS.ActiveConnection = objConn
objRS.CursorType = adOpenForwardOnly
objRS.LockType = adLockReadOnly
objRS.Open Application("SQL")
If objRS.EOF Then
Response.Write("No Records Found")
Else
'write headings
Response.Write( _
"TABLE BORDER=1>" _
"TR>" _
"TH>OrderID/TH>" _
"TH>CustomerID/TH>" _
"TH>EmployeeID/TH>" _
"TH>OrderDate/TH>" _
"TH>RequiredDate/TH>" _
"TH>ShippedDate/TH>" _
"TH>Freight/TH>" _
"/TR>" _
)
'write data
Do While Not objRS.EOF
Response.Write( _
"TR>" _
"TD>" objRS("OrderID") "/TD>" _
"TD>" objRS("CustomerID") "/TD>" _
"TD>" objRS("EmployeeID") "/TD>" _
"TD>" objRS("OrderDate") "/TD>" _
"TD>" objRS("RequiredDate") "/TD>" _
"TD>" objRS("ShippedDate") "/TD>" _
"TD>" objRS("Freight") "/TD>" _
"/TR> " _
)
objRS.MoveNext
Loop
Response.Write("/TABLE>")
End If
objRS.Close
objConn.Close
Set objRS = Nothing
Set objConn = Nothing
Response.Write("/BODY>/HTML>")
%>
下面是測試結(jié)果:
我們來看一下各欄數(shù)字的含義:
0返回0個記錄的頁面所需要的TTLB(毫秒)。在所有的測試中,該值被視為生成頁面本身(包括創(chuàng)建對象)的時間開銷,不包含循環(huán)訪問記錄集數(shù)據(jù)的時間。
25以毫秒計的提取和顯示25個記錄的TTLB
tot time/25"25"欄的TTLB除以25,它是每個記錄的總計平均時間開銷。
disp time/25"25"欄的TTLB減去"0"欄的TTLB,然后除以25。該值反映了在循環(huán)記錄集時顯示單個記錄所需時間。
250提取和顯示250個記錄的TTLB。
tot time/250"250"欄的TTLB除以25,該值代表單個記錄的總計平均時間開銷。
disp time/250"250"欄的TTLB減去"0"欄的TTLB,再除以250。該值反映了在循環(huán)記錄集時顯示單個記錄所需時間。
上面的測試結(jié)果將用來同下一個測試結(jié)果比較。
四、是否應(yīng)該通過包含引用ADOVBS.inc?
Microsoft提供的ADOVBS.inc包含了270行代碼,這些代碼定義了大多數(shù)的ADO屬性常量。我們這個示例只從ADOVBS.inc引用了2個常量。因此本次測試(ADO__02.asp)中我們刪除了包含文件引用,設(shè)置屬性時直接使用相應(yīng)的數(shù)值。
objRS.CursorType = 0?' adOpenForwardOnly
objRS.LockType = 1' adLockReadOnly
可以看到頁面開銷下降了23%。該值并不影響單個記錄的提取和顯示時間,因為這里的變化不會影響循環(huán)內(nèi)的記錄集操作。有多種方法可以解決ADOVBS.inc的引用問題。我們建議將ADOVBS.inc文件作為參考,設(shè)置時通過注釋加以說明。請記住,正如第一部分所指出的,適度地運用注釋對代碼的效率影響極小。另外一種方法是將那些需要用到的常量從ADOVBS.inc文件拷貝到頁面內(nèi)。
還有一個解決該問題的好方法,這就是通過鏈接ADO類型庫使得所有的ADO常量直接可用。把下面的代碼加入Global.asa文件,即可直接訪問所有的ADO常量:
!--METADATA TYPE="typelib"
FILE="C:Program FilesCommon FilesSYSTEMADOmsado15.dll"
NAME="ADODB Type Library" -->
或者:
!--METADATA TYPE="typelib"
UUID="00000205-0000-0010-8000-00AA006D2EA4"
NAME="ADODB Type Library" -->
因此,我們的第一條規(guī)則為:
l避免包含ADOVBS.inc文件,通過其他方法訪問和使用ADO常量。
五、使用記錄集時是否應(yīng)該創(chuàng)建單獨的連接對象?
要正確地回答這個問題,我們必須分析兩種不同條件下的測試:第一,頁面只有一個數(shù)據(jù)庫事務(wù);第二,頁面有多個數(shù)據(jù)庫事務(wù)。
在前例中,我們創(chuàng)建了一個單獨的Connection對象并將它賦給Recordset的ActiveConnection屬性。然而,如ADO__03.asp所示,我們也可以直接把連接串賦給ActiveConnection屬性,在腳本中初始化和配置Connection對象這一額外的步驟可以省去。
objRS.ActiveConnection = Application("Conn")
雖然Recordset對象仍舊要創(chuàng)建一個連接,但此時的創(chuàng)建是在高度優(yōu)化的條件下進(jìn)行的。因此,與上一次測試相比,頁面開銷又下降了23%,而且如預(yù)期的一樣,單個記錄的顯示時間沒有實質(zhì)的變化。
因此,我們的第二個規(guī)則如下:
l如果只使用一個記錄集,直接把連接串賦給ActiveConnection屬性。
接下來我們檢查頁面用到多個記錄集時,上述規(guī)則是否仍舊有效。為測試這種情形,我們引入一個FOR循環(huán)將前例重復(fù)10次。在這個測試中,我們將研究三種變化:
第一,如ADO__04.asp所示,在每一個循環(huán)中建立和拆除Connection對象:
復(fù)制代碼 代碼如下:
Dim i
For i = 1 to 10
Set objConn = Server.CreateObject("ADODB.Connection")
objConn.Open Application("Conn")
Set objRS = Server.CreateObject("ADODB.Recordset")
objRS.ActiveConnection = objConn
objRS.CursorType = 0 'adOpenForwardOnly
objRS.LockType = 1 'adLockReadOnly
objRS.Open Application("SQL")
If objRS.EOF Then
Response.Write("No Records Found")
Else
'write headings
...
'write data
...
End If
objRS.Close
Set objRS = Nothing
objConn.Close
Set objConn = Nothing
Next
第二,如ADO__05.asp所示,在循環(huán)外面創(chuàng)建Connection對象,所有記錄集共享該對象:
復(fù)制代碼 代碼如下:
Set objConn = Server.CreateObject("ADODB.Connection")
objConn.Open Application("Conn")
Dim i
For i = 1 to 10
Set objRS = Server.CreateObject("ADODB.Recordset")
objRS.ActiveConnection = objConn
objRS.CursorType = 0 'adOpenForwardOnly
objRS.LockType = 1 'adLockReadOnly
objRS.Open Application("SQL")
If objRS.EOF Then
Response.Write("No Records Found")
Else
'write headings
...
'write data
...
End If
objRS.Close
Set objRS = Nothing
Next
objConn.Close
Set objConn = Nothing
第三,如ADO__06.asp所示,在每一個循環(huán)內(nèi)把連接串賦給ActiveConnection屬性:
復(fù)制代碼 代碼如下:
Dim i
For i = 1 to 10
Set objRS = Server.CreateObject("ADODB.Recordset")
objRS.ActiveConnection = Application("Conn")
objRS.CursorType = 0 'adOpenForwardOnly
objRS.LockType = 1 'adLockReadOnly
objRS.Open Application("SQL")
If objRS.EOF Then
Response.Write("No Records Found")
Else
'write headings
...
'write data
...
End If
objRS.Close
Set objRS = Nothing
Next
就象我們可以猜想到的一樣,在循環(huán)內(nèi)創(chuàng)建和拆除連接對象是效率最差的方法。不過,令人驚異的是,在循環(huán)內(nèi)直接把連接串賦給ActiveConnection屬性只比共享單個連接對象稍微慢了一點。
盡管如此,第三規(guī)則應(yīng)該為:
l同一頁面內(nèi)用到多個記錄集時,創(chuàng)建單一的連接對象并通過ActiveConnection屬性共享它。
六、哪種游標(biāo)類型和記錄鎖定方式效率最高?
迄今為止的所有測試中我們只使用了“只能向前”的游標(biāo)來訪問記錄集。ADO為記錄集提供的游標(biāo)還有三種類型:靜態(tài)可滾動的游標(biāo),動態(tài)可滾動的游標(biāo),鍵集游標(biāo)。每種游標(biāo)都提供不同的功能,比如訪問前一記錄和后一記錄、是否可以看到其他程序?qū)?shù)據(jù)的修改等。不過,具體討論每一種游標(biāo)類型的功用已經(jīng)超出了本文的范圍,下表是各種游標(biāo)類型的一個比較性的分析。
和“只能向前”類型的游標(biāo)相比,所有其它的游標(biāo)類型都需要額外的開銷,而且這些游標(biāo)在循環(huán)內(nèi)一般也要慢一些。因此,我們愿與您共享如下告誡:永遠(yuǎn)不要這樣認(rèn)為——“唔,有時候我會用到動態(tài)游標(biāo),那么我就一直使用這種游標(biāo)吧?!?
同樣的看法也適用于記錄鎖定方式的選擇。前面的測試只用到了只讀的加鎖方式,但還存在其他三種方式:保守式、開放式、開放式批處理方式。和游標(biāo)類型一樣,這些鎖定方式為處理記錄集數(shù)據(jù)提供了不同的功能和控制能力。
我們得出如下規(guī)則:
l使用適合于處理任務(wù)的最簡單的游標(biāo)類型和記錄鎖定方式。
七、最好用哪種方法提取記錄集?
到目前為止我們一直通過創(chuàng)建Recordset對象提取記錄集,但是ADO也提供了間接的記錄集提取方法。下面的測試比較ADO__03.asp和直接從Connection對象創(chuàng)建記錄集(CONN_01.asp)這兩種方法:
復(fù)制代碼 代碼如下:
Set objConn = Server.CreateObject("ADODB.Connection")
objConn.Open Application("Conn")
Set objRS = objConn.Execute(Application("SQL"))
可以看到頁面開銷略有增加,單個記錄的顯示時間沒有變化。
下面我們再來看看從Command對象直接創(chuàng)建記錄集對象(CMD__02.asp):
復(fù)制代碼 代碼如下:
Set objCmd = Server.CreateObject("ADODB.Command")
objCmd.ActiveConnection = Application("Conn")
objCmd.CommandText = Application("SQL")
Set objRS = objCmd.Execute
同樣,頁面開銷也略有增加,而單個記錄的顯示時間沒有本質(zhì)的變化。后面這兩種方法在性能上的差異很小,但我們還有一個重要的問題需要考慮。
通過Recordset類創(chuàng)建記錄集時,我們能夠以最大的靈活性控制記錄集的處理方式。既然后面兩種方法未能有壓倒性的性能表現(xiàn),我們主要還是考慮默認(rèn)返回的游標(biāo)類型和記錄鎖定方式,對于某些場合來說默認(rèn)值并不一定是最理想的。
因此,除非由于特殊的原因需要選擇后面兩種方法,否則我們建議考慮下面的規(guī)則:
l通過ADODB.Recordset類實例化記錄集,以獲得最好的性能和靈活性。
八、是否應(yīng)該使用本地記錄集?
ADO允許使用本地(客戶端)記錄集,此時查詢將提取記錄集內(nèi)的所有數(shù)據(jù),查詢完成后連接可以立即關(guān)閉,以后使用本地的游標(biāo)訪問數(shù)據(jù),這為釋放連接帶來了方便。使用本地記錄集對于訪問那些要求數(shù)據(jù)離線使用的遠(yuǎn)程數(shù)據(jù)服務(wù)非常重要,那么,對于普通的應(yīng)用它是否同樣有所幫助?
下面我們加入CursorLocation屬性,并在打開記錄集之后關(guān)閉了連接(CLIENT1.asp):
復(fù)制代碼 代碼如下:
Set objRS = Server.CreateObject("ADODB.Recordset")
objRS.CursorLocation = 2' adUseClient
objRS.ActiveConnection = Application("Conn")
objRS.LockType = 1?' adLockReadOnly
objRS.Open Application("SQL")
objRS.ActiveConnection = Nothing
理論上,這種方法由于以下兩個原因會對效率有所好處:第一,它避免了在記錄之間移動時重復(fù)地通過連接請求數(shù)據(jù);第二,由于能夠方便地釋放連接,它減輕了資源需求。然而,從上表看起來使用本地記錄集對提高效率顯然沒有什么幫助。這或許是因為使用本地記錄集時,不管程序設(shè)置的是什么,游標(biāo)總是變成靜態(tài)類型。
第6個規(guī)則如下:
l除非確實要求記錄集本地化,否則應(yīng)避免使用。
十、用哪種方法引用記錄集字段值效率最高?
10.1 測試
至此為止我們一直通過名字引用記錄集中的字段值。由于這種方法要求每次都必須尋找相應(yīng)的字段,它的效率并不高。為證明這一點,下面這個測試中我們通過字段在集合中的索引引用它的值(ADO__08.asp):
復(fù)制代碼 代碼如下:
'write data
Do While Not objRS.EOF
Response.Write( _
"TR>" _
"TD>" objRS(0) "/TD>" _
"TD>" objRS(1) "/TD>" _
"TD>" objRS(2) "/TD>" _
"TD>" objRS(3) "/TD>" _
"TD>" objRS(4) "/TD>" _
"TD>" objRS(5) "/TD>" _
"TD>" objRS(6) "/TD>" _
"/TR> " _
)
objRS.MoveNext
Loop
和預(yù)期的一樣,頁面開銷也有小小的變化(這或許是因為代碼略有減少)。然而,這種方法在顯示時間上的改善是相當(dāng)明顯的。
在下一個測試中,我們把所有的字段分別綁定到變量(ADO__09.asp):
復(fù)制代碼 代碼如下:
If objRS.EOF Then
Response.Write("No Records Found")
Else
'write headings
...
Dim fld0
Dim fld1
Dim fld2
Dim fld3
Dim fld4
Dim fld5
Dim fld6
Set fld0 = objRS(0)
Set fld1 = objRS(1)
Set fld2 = objRS(2)
Set fld3 = objRS(3)
Set fld4 = objRS(4)
Set fld5 = objRS(5)
Set fld6 = objRS(6)
'write data
Do While Not objRS.EOF
Response.Write( _
"TR>" _
"TD>" fld0 "/TD>" _
"TD>" fld1 "/TD>" _
"TD>" fld2 "/TD>" _
"TD>" fld3 "/TD>" _
"TD>" fld4 "/TD>" _
"TD>" fld5 "/TD>" _
"TD>" fld6 "/TD>" _
"/TR>" _
)
objRS.MoveNext
Loop
Set fld0 = Nothing
Set fld1 = Nothing
Set fld2 = Nothing
Set fld3 = Nothing
Set fld4 = Nothing
Set fld5 = Nothing
Set fld6 = Nothing
Response.Write("/TABLE>")
End If
這是目前為止最好的記錄。請注意單個記錄的顯示時間已經(jīng)降低到0.45毫秒以下。
上述腳本都要求對結(jié)果記錄集的構(gòu)造有所了解。例如,我們在列標(biāo)題中直接使用了字段名字,單獨地引用各個字段值。下面這個測試中,不僅字段數(shù)據(jù)通過遍歷字段集合得到,而且字段標(biāo)題也用同樣的方式得到,這是一種更為動態(tài)的方案(ADO__10.asp)。
復(fù)制代碼 代碼如下:
If objRS.EOF Then
Response.Write("No Records Found")
Else
'write headings Response.Write("TABLE BORDER=1>TR>")
For Each objFld in objRS.Fields
Response.Write("TH>" objFld.name "/TH>")
Next
Response.Write("/TR>")
'write data
Do While Not objRS.EOF
Response.Write("TR>")
For Each objFld in objRS.Fields
? Response.Write("TD>" objFld.value "/TD>")
Next
Response.Write("/TR>")
objRS.MoveNext
Loop
Response.Write("/TABLE>")
End If
可以看到,代碼性能有所下降,但它仍舊要比ADO__07.asp要快。
下一個測試示例是前面兩個方法的折衷。我們將繼續(xù)保持動態(tài)特征,同時通過在動態(tài)分配的數(shù)組中保存字段引用提高性能:
復(fù)制代碼 代碼如下:
If objRS.EOF Then
Response.Write("No Records Found")
Else
Dim fldCount
fldCount = objRS.Fields.Count
Dim fld()
ReDim fld(fldCount)
Dim i
For i = 0 to fldCount-1
Set fld(i) = objRS(i)
Next
'write headings
Response.Write("TABLE BORDER=1>TR>") For i = 0 to fldCount-1
Response.Write("TH>" fld(i).name "/TH>")
Next
Response.Write("/TR>")
'write data
Do While Not objRS.EOF
Response.Write("TR>")
For i = 0 to fldCount-1
Response.Write("TD>" fld(i) "/TD>")
Next
Response.Write("/TR>")
objRS.MoveNext
Loop
For i = 0 to fldCount-1
Set fld(i) = Nothing
Next
Response.Write("/TABLE>")
End If
雖然還不能超過以前最好的成績,但它比開頭的幾個示例要快,同時它具有動態(tài)地處理任何記錄集這一優(yōu)點。
與前面的測試代碼相比,下面的測試代碼有了根本性的改動。它使用記錄集對象的GetRows方法填充數(shù)組以供循環(huán)訪問數(shù)據(jù),而不是直接訪問記錄集本身。注意在調(diào)用GetRows之后立即把Recordset設(shè)置成了Nothing,也就是盡快地釋放了系統(tǒng)資源。另外,請注意數(shù)組的第一維代表字段,第二維代表行(ADO__12.asp)。
復(fù)制代碼 代碼如下:
If objRS.EOF Then
Response.Write("No Records Found")
objRS.Close
Set objRS = Nothing
Else
'write headings
...
'set array
Dim arrRS
arrRS = objRS.GetRows
'close recordset early
objRS.Close
Set objRS = Nothing
'write data
Dim numRows
Dim numFlds
Dim row
Dim fld
numFlds = Ubound(arrRS, 1)
numRows = Ubound(arrRS, 2)
For row= 0 to numRows
Response.Write("TR>")
For fld = 0 to numFlds
Response.Write("TD>" arrRS(fld, row) "/TD>")
Next
Response.Write("/TR>")
Next
Response.Write("/TABLE>")
End If
使用GetRows方法時,整個記錄集都被提取到了數(shù)組。雖然記錄集極端龐大時可能產(chǎn)生資源問題,但是用循環(huán)訪問數(shù)據(jù)的速度確實更快了,這是由于取消了MoveNext和檢查EOF之類的函數(shù)調(diào)用。
速度是要付出代價的,現(xiàn)在記錄集的元數(shù)據(jù)已經(jīng)丟失了。為解決這個問題,我們可以在調(diào)用GetRows之前從記錄集對象提取標(biāo)題信息;此外,數(shù)據(jù)類型和其他信息也可以預(yù)先提取。另外還要注意的是,測試中性能上的優(yōu)勢只有在記錄集較大的時候才會出現(xiàn)。
這一組的最后一個測試中,我們使用了記錄集的GetString方法。GetString方法將整個記錄集提取成為一個大的字符串,并允許指定分隔符(ADO__13.asp):
復(fù)制代碼 代碼如下:
If objRS.EOF Then
Response.Write("No Records Found")
objRS.Close
Set objRS = Nothing
Else
'write headings
...
'set array
Dim strTable
strTable = objRS.GetString (2, , "/TD>TD>", "/TD>/TR>TR>TD>")
'close recordset early
objRS.Close
Set objRS = Nothing
Response.Write(strTable "/TD>/TR>/TABLE>")
End If