提高ASP性能的最佳選擇
發(fā)表時(shí)間:2024-01-24 來(lái)源:明輝站整理相關(guān)軟件相關(guān)文章人氣:
[摘要]ASP開發(fā)人員為了在他們的設(shè)計(jì)項(xiàng)目中獲得更好的性能和可擴(kuò)展性而不斷努力。幸運(yùn)地是,有許多書籍和站點(diǎn)在這方面提供了很好的建議。但是這些建議的基礎(chǔ)都是從ASP平臺(tái)工作的結(jié)構(gòu)上所得出的結(jié)論,對(duì)實(shí)際獲得的性能的提高沒(méi)有量的測(cè)量。由于這些建議需要更加復(fù)雜的編碼過(guò)程并降低了編碼的可讀性,開發(fā)人員就只能在看不到...
ASP開發(fā)人員為了在他們的設(shè)計(jì)項(xiàng)目中獲得更好的性能和可擴(kuò)展性而不斷努力。幸運(yùn)地是,有許多書籍和站點(diǎn)在這方面提供了很好的建議。但是這些建議的基礎(chǔ)都是從ASP平臺(tái)工作的結(jié)構(gòu)上所得出的結(jié)論,對(duì)實(shí)際獲得的性能的提高沒(méi)有量的測(cè)量。由于這些建議需要更加復(fù)雜的編碼過(guò)程并降低了編碼的可讀性,開發(fā)人員就只能在看不到實(shí)際運(yùn)行效果的情況下,獨(dú)自衡量為了提高他們ASP應(yīng)用程序的性能是否值得付出這些代價(jià)。
本文分為兩大部分,我將介紹一些性能測(cè)試結(jié)果,幫助開發(fā)人員來(lái)確定某一特定舉措是否不僅對(duì)將來(lái)的項(xiàng)目來(lái)說(shuō)是值得的,并且能夠?qū)υ瓉?lái)的項(xiàng)目進(jìn)行更新。在第一部分我將回顧一些ASP開發(fā)的基礎(chǔ)性問(wèn)題。在第二部分,將涉及一些最優(yōu)化ADO函數(shù),并將它們的結(jié)果與調(diào)用VB COM對(duì)象執(zhí)行相同ADO函數(shù)的ASP頁(yè)面進(jìn)行比較。這些結(jié)果很讓人開眼界,甚至有些時(shí)候是很令人吃驚的。
在本文中,我們將回答以下問(wèn)題:
* 將ASP生成的內(nèi)容寫入響應(yīng)流中最有效的方法是什么?
* 是否應(yīng)該開啟緩沖器?
* 是否應(yīng)該考慮向ASP代碼中增加注釋?
* 是否應(yīng)該為頁(yè)面明確地設(shè)置默認(rèn)語(yǔ)言?
* 如果不需要,是否應(yīng)該關(guān)閉Session 狀態(tài)?
* 是否應(yīng)該把腳本邏輯放在子程序和函數(shù)區(qū)中?
* 使用包含文件有什么影響?
* 執(zhí)行錯(cuò)誤處理時(shí)會(huì)施加什么樣的負(fù)載?
* 設(shè)置一個(gè)上下文處理是否對(duì)性能有影響?
所有測(cè)試都是用Microsoft的Web應(yīng)用程序重點(diǎn)工具(WAST)來(lái)進(jìn)行的,這是一個(gè)免費(fèi)的工具,可以在這里找到。我用WAST創(chuàng)建了一個(gè)簡(jiǎn)單的test 腳本,反復(fù)調(diào)用下面所描述的ASP頁(yè)面測(cè)試(每個(gè)超過(guò)70,000次)。反應(yīng)的時(shí)間基于平均最后字節(jié)總時(shí)間(TTLB), 也就是從最初請(qǐng)求的時(shí)間到工具從服務(wù)器接收最后一位數(shù)據(jù)的時(shí)間。我們的測(cè)試服務(wù)器是一個(gè)Pentium 166,內(nèi)存為196MB,客戶機(jī)為Pentium 450,內(nèi)存為256MB。你也許會(huì)想這些機(jī)器的性能并不算很高級(jí),但是不要忘了,我們并不是要測(cè)試服務(wù)器的容量,我們只是要測(cè)試服務(wù)器每次處理一個(gè)頁(yè)面所用的時(shí)間。測(cè)試期間這些機(jī)器不做其它工作。WAST 測(cè)試腳本、測(cè)試報(bào)告以及所有的ASP測(cè)試頁(yè)面都包含在ZIP文件中,你可以自己進(jìn)行回顧和測(cè)試。
將ASP生成的內(nèi)容寫入響應(yīng)流中最有效的方法是什么?
使用ASP的一個(gè)最主要原因是在服務(wù)器上生成動(dòng)態(tài)內(nèi)容。所以很明顯,我們測(cè)試的起點(diǎn)是確定將動(dòng)態(tài)內(nèi)容發(fā)送到響應(yīng)流中的最適合的方式。在多種選擇中,有兩個(gè)是最基本的:一是使用內(nèi)聯(lián)ASP標(biāo)記,另一個(gè)是使用Response.Write 語(yǔ)句。
為測(cè)試這些選擇,我們創(chuàng)建了一個(gè)簡(jiǎn)單的ASP頁(yè)面,其中定義了一些變量,然后將它們的值插入表格中。雖然這個(gè)頁(yè)面很簡(jiǎn)單也不是很實(shí)用,但它允許我們分離并測(cè)試一些單獨(dú)的問(wèn)題。
使用ASP內(nèi)聯(lián)標(biāo)記
第一個(gè)測(cè)試包括使用內(nèi)聯(lián)ASP標(biāo)記< %= x % >,其中x是一個(gè)已賦值的變量。到目前為止,這個(gè)方法是最容易執(zhí)行的,并且它使頁(yè)面的HTML部分保持一種易于閱讀和維護(hù)的格式。
< % 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的完整代碼
以前的最佳(反應(yīng)速度) = 8.28 msec/page
在HTML的每一行使用Response.Write 語(yǔ)句
許多比較好的學(xué)習(xí)文檔建議避免使用前面的那種方法。其主要理由是,在輸出頁(yè)面和處理頁(yè)面施加反應(yīng)時(shí)間的過(guò)程中,如果web 服務(wù)器不得不在發(fā)送純HTML和處理腳本之間進(jìn)行轉(zhuǎn)換,就會(huì)發(fā)生一種被稱為上下文轉(zhuǎn)換的問(wèn)題。大部分程序員一聽到這里,他們的第一反應(yīng)就是將原始的HTML的每一行都包裝在Response.Write函數(shù)中。
…
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的片段
以前的最佳(反應(yīng)速度) = 8.28 msec/page
反應(yīng)時(shí)間 = 8.08 msec/page
差= -0.20 msec (減少 2.4%)
我們可以看到,使用這種方法與使用內(nèi)聯(lián)標(biāo)記的方法相比在性能上獲得的收益非常小,這也許是因?yàn)轫?yè)面給服務(wù)器裝載了一大堆小的函數(shù)調(diào)用。這種方法最大的缺點(diǎn)是,由于現(xiàn)在HTML都嵌入腳本中,所以腳本代碼變得更加冗長(zhǎng),更加難以閱讀和維護(hù)。
使用包裝函數(shù)
當(dāng)我們?cè)噲D使用Response.Write 語(yǔ)句這種方法時(shí),最令人灰心的發(fā)現(xiàn)可能就是Response.Write 函數(shù)不能在每行的結(jié)尾處放置一個(gè)CRLF 。因此,當(dāng)你從瀏覽器中閱讀源代碼時(shí),本來(lái)布置得非常好的HTML,現(xiàn)在成了沒(méi)有結(jié)束的一行。我想,你的下一個(gè)發(fā)現(xiàn)可能會(huì)更令你恐怖:在Response 對(duì)象中沒(méi)有其姊妹函數(shù)Writeln 。所以,一個(gè)很明顯的反應(yīng)就是為Response.Write 函數(shù)創(chuàng)建一個(gè)包裝函數(shù),以便給每一行都附加一個(gè)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的片段
以前的最佳(反應(yīng)速度)= 8.08 msec/page
反應(yīng)時(shí)間= 10.11 msec/page
差 = +2.03 msec (增加 25.1%)
當(dāng)然,由于這種方法有效地使函數(shù)調(diào)用次數(shù)加倍,其對(duì)性能的影響也很明顯,因此要不惜一切代價(jià)避免。具有諷刺意味的是CRLF也向反應(yīng)流中為每行增加了2個(gè)字節(jié),而這是瀏覽器不需要呈現(xiàn)到頁(yè)面上的。格式化良好的HTML所做的一切就是讓你的競(jìng)爭(zhēng)者更容易閱讀你的HTML源代碼并理解你的設(shè)計(jì)。
將連續(xù)的Response.Write 連接到一個(gè)單獨(dú)語(yǔ)句中
不考慮我們前面用包裝函數(shù)進(jìn)行的測(cè)試,下一個(gè)合乎邏輯的步驟就是從單獨(dú)的Response.Write 語(yǔ)句中提取出所有的字符串,將它們連接到一個(gè)單獨(dú)語(yǔ)句中,這樣就減少了函數(shù)調(diào)用的次數(shù),極大地提高了頁(yè)面的性能。
…
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的片段
以前的最佳(反應(yīng)速度)= 8.08 msec/page
反應(yīng)時(shí)間 = 7.05 msec/page
差 = -1.03 msec (減少12.7%)
目前,這是最優(yōu)化的配置。
將連續(xù)的Response.Write 連接到一個(gè)單獨(dú)語(yǔ)句中,在每行結(jié)尾處增加一個(gè)CRLF
考慮到那些要求他們的源代碼從瀏覽器中看要很純粹的人,我用vbCRLF 常量在前面測(cè)試中每行的結(jié)尾處插入了一些回車,然后重新運(yùn)行。
…
Response.Write("< html >" & vbCRLF & _
"< head >" & vbCRLF & _
" < title >Response Test< /title >" & vbCRLF & _
"< /head >" & vbCRLF & _
…
/app1/response5.asp的片段
前面的最佳(反應(yīng)速度)= 7.05 msec/page
反應(yīng)時(shí)間= 7.63 msec/page
差 = +0.58 msec (增加 8.5%)
運(yùn)行的結(jié)果在性能上有一點(diǎn)降低,這也許是由于額外的串聯(lián)和增加的字符量。
回顧和觀測(cè)
從前面有關(guān)ASP輸出的測(cè)試中可以得出一些規(guī)則:
* 避免內(nèi)聯(lián)ASP的過(guò)多使用。
* 總是將連續(xù)Response.Write 語(yǔ)句連接進(jìn)一個(gè)單獨(dú)語(yǔ)句內(nèi)。
* 永遠(yuǎn)不要在Response.Write 周圍使用包裝函數(shù)來(lái)附加CRLF。
* 如果必須格式化HTML輸出,直接在Response.Write 語(yǔ)句內(nèi)附加CRLF。
是否應(yīng)該開啟緩沖器?
通過(guò)腳本程序啟動(dòng)緩沖器
在ASP腳本的頂部包含Response.Buffer=True ,IIS就會(huì)將頁(yè)面的內(nèi)容緩存。
< % OPTION EXPLICIT
Response.Buffer = true
Dim FirstName
…
/app1/buffer__1.asp的片段
以前的最佳(反應(yīng)時(shí)間)= 7.05 msec/page
反應(yīng)時(shí)間 = 6.08 msec/page
差= -0.97 msec (降低13.7%)
性能得到了極大提高。但是等等,還能有更好的。
通過(guò)服務(wù)器配置啟動(dòng)緩沖器
雖然在IIS 5.0中緩沖器是被默認(rèn)啟動(dòng)的,但是在IIS 4.0中還必須手動(dòng)來(lái)啟動(dòng)它。這時(shí)要找到站點(diǎn)的Properties 對(duì)話框,在那里,從Home Directory 標(biāo)簽中選擇配置按鈕。然后在"App options"下選擇"enable buffering" 。對(duì)于這個(gè)測(cè)試,Response.Buffer 語(yǔ)句從腳本中被移走了。
以前的最佳= 7.05 msec/page
反應(yīng)時(shí)間 = 5.57 msec/page
差= -1.48 msec (降低 21.0%)
目前,這是我們所得到的最快反應(yīng)了,比我們以前最好情況下的反應(yīng)時(shí)間還要降低21%。從現(xiàn)在開始,我們以后的測(cè)試都要把這個(gè)反應(yīng)時(shí)間作為基準(zhǔn)值。
回顧及觀測(cè)
緩沖器是提高性能的好方法,所以把緩沖器設(shè)置成服務(wù)器的默認(rèn)值很有必要。如果因?yàn)槟承┰颍?yè)面不能正確地使緩沖器運(yùn)行,只需要Response.Buffer=False 命令即可。緩沖器的一個(gè)缺點(diǎn)是在整個(gè)頁(yè)面處理完之前,用戶從服務(wù)器看不到任何東西。因此,在復(fù)雜頁(yè)面的處理期間,偶而調(diào)用一次Response.Flush 來(lái)更新用戶是個(gè)好主意。
現(xiàn)在在我們的規(guī)則中又增加了一條:總是通過(guò)服務(wù)器設(shè)置開啟緩沖器。
是否應(yīng)該考慮向ASP代碼中增加注釋?
大部分HTML開發(fā)人員都知道包含HTML注釋不是個(gè)好主意,首先會(huì)增加傳輸數(shù)據(jù)的規(guī)模,其次它們只是向別的開發(fā)人員提供有關(guān)你頁(yè)面組織的信息。但是ASP頁(yè)面上的注釋又如何呢?它們從來(lái)不離開服務(wù)器,但也確實(shí)要增加頁(yè)面的規(guī)模,因此必須用ASP進(jìn)行分解。
在這次的測(cè)試中,我們?cè)黾?0條注釋,每條有80個(gè)字符,總共有1600個(gè)字符。
< % OPTION EXPLICIT
'-------------------------------------------------------------------------------
… 20 lines …
'-------------------------------------------------------------------------------
Dim FirstName
…
/app2/comment_1.asp片段
基準(zhǔn)= 5.57 msec/page
反應(yīng)時(shí)間= 5.58 msec/page
差 = +0.01 msec (增加 0.1%)
測(cè)試的結(jié)果是驚人的。雖然注釋幾乎相當(dāng)于文件本身的兩倍,但是它們的存在并沒(méi)有給反應(yīng)時(shí)間帶來(lái)很大的影響。所以說(shuō)我們可以遵循以下規(guī)則:
只要使用適度,ASP注釋對(duì)性能的影響很小或根本沒(méi)有影響。
是否應(yīng)該為頁(yè)面明確地設(shè)置默認(rèn)語(yǔ)言?
IIS處理VBScript是默認(rèn)的設(shè)置,但是我看到,在大多數(shù)例子中還是用< %@LANGUAGE=VBSCRIPT% >聲明將語(yǔ)言明確地設(shè)置為VBScript 。我們的下一個(gè)測(cè)試將檢驗(yàn)這個(gè)聲明的存在對(duì)性能有什么影響。
< %@ LANGUAGE=VBSCRIPT % >
< % OPTION EXPLICIT
Dim FirstName
…
/app2/language1.asp片段。
基準(zhǔn)值= 5.57 msec/page
反應(yīng)時(shí)間= 5.64 msec/page
差= +0.07 msec (增加1.2%)
可以看到,包含了語(yǔ)言的聲明對(duì)性能有一個(gè)輕微的影響。因此:
* 設(shè)置服務(wù)器的默認(rèn)語(yǔ)言配置以與站點(diǎn)上使用的語(yǔ)言相匹配。
* 除非你使用非默認(rèn)語(yǔ)言,不要設(shè)置語(yǔ)言聲明。
如果不需要,是否應(yīng)該關(guān)閉Session 狀態(tài)?
避免使用IIS的Session上下文有許多理由,那些已經(jīng)可以獨(dú)立成為一篇文章。我們現(xiàn)在試圖回答的問(wèn)題是當(dāng)頁(yè)面不需要時(shí),關(guān)閉Session上下文是否對(duì)性能提高有所幫助。從理論上講應(yīng)該是肯定的,因?yàn)檫@樣一來(lái)就不需要用頁(yè)面例示Session上下文了。
同緩沖器一樣,Session狀態(tài)也有兩種配置方法:通過(guò)腳本和通過(guò)服務(wù)器設(shè)置。
通過(guò)腳本關(guān)閉Session上下文
對(duì)于這個(gè)測(cè)試,要關(guān)閉頁(yè)面中的Session上下文,我增加一個(gè)Session狀態(tài)聲明。
< %@ ENABLESESSIONSTATE = FALSE % >
< % OPTION EXPLICIT
Dim FirstName
…
/app2/session_1.asp片段。
基準(zhǔn)值= 5.57 msec/page
反應(yīng)時(shí)間= 5.46 msec/page
差= -0.11 msec (降低2.0%)
只通過(guò)這樣一個(gè)小小的努力就得到了不錯(cuò)的進(jìn)步,F(xiàn)在看看第二部分。
通過(guò)服務(wù)器配置關(guān)閉Session 上下文
要在服務(wù)器上關(guān)閉Session 上下文,請(qǐng)到站點(diǎn)的Properties 對(duì)話框。在Home Directory 標(biāo)簽上選擇Configuration 按鈕。然后在"App options"下取消"enable session state" 的選擇。我們?cè)跊](méi)有ENABLESESSIONSTATE 聲明的情況下運(yùn)行測(cè)試。
基準(zhǔn)值 = 5.57 msec/page
反應(yīng)時(shí)間= 5.14 msec/page
差= -0.43 msec (降低7.7%)
這是性能的又一個(gè)顯著提高。所以,我們的規(guī)則應(yīng)是:在不需要的情況下,總是在頁(yè)面或應(yīng)用程序的水平上關(guān)閉Session狀態(tài)。
使用Option Explicit 會(huì)使性能有實(shí)質(zhì)改變嗎?
在一個(gè)ASP頁(yè)面的頂部設(shè)置Option Explicit 以要求所有的變量在使用之前都要在頁(yè)面上進(jìn)行聲明。這有兩個(gè)原因。首先應(yīng)用程序可以更快地處理變量的存取。其次,這樣可以防止我們無(wú)意中錯(cuò)用變量的名字。在這個(gè)測(cè)試中我們移走Option Explicit 引用和變量的Dim 聲明。
基準(zhǔn)值 = 5.57 msec/page
反應(yīng)時(shí)間= 6.12 msec/page
差 = +0.55 msec (9.8% 增加)、
盡管有一些代碼行從頁(yè)面中去掉了,反應(yīng)時(shí)間卻依然增加了。所以盡管使用Option explicit 有時(shí)候費(fèi)時(shí)間,但是在性能上卻有很顯著的效果。因此我們又可以增加一條規(guī)則:在VBScript中總是使用Option explicit。
是否應(yīng)該把腳本邏輯放在子程序和函數(shù)區(qū)?
用函數(shù)和子程序來(lái)組織和管理代碼是一個(gè)很好的方法,特別是當(dāng)一個(gè)代碼區(qū)在頁(yè)面中多次使用的情況。缺點(diǎn)是要在系統(tǒng)上增加一個(gè)做相同工作的額外函數(shù)調(diào)用。子程序和函數(shù)的另一個(gè)問(wèn)題是變量的范圍。從理論上說(shuō),在一個(gè)函數(shù)區(qū)內(nèi)指定變量更有效,F(xiàn)在我們看看這兩個(gè)方面如何發(fā)生作用。
將Response.Write 語(yǔ)句移入子程序
這個(gè)測(cè)試只是將Response.Write 語(yǔ)句移入一個(gè)子程序區(qū)內(nèi)。
…
CALL writeTable()
SUB writeTable()
Response.Write("< html >" & _
"< head >" & _
…
"< tr >< td >< b >EMail:< /b >< /td >< td >" & EMail & "< /td >< /tr >" & _
"< tr >< td >< b >Birth Date:< /b >< /td >< td >" & BirthDate & "< /td >< /tr >" & _
"< /table >" & _
"< /body >" & _
"< /html >")
END SUB
/app2/function1.asp片段
基準(zhǔn)值= 5.57 msec/page
反應(yīng)時(shí)間= 6.02 msec/page
差 = +0.45 msec (8.1% 增加)
同預(yù)料中一樣,子程序調(diào)用給頁(yè)面帶來(lái)了額外的負(fù)擔(dān)。
將所有腳本移入子程序中
在這個(gè)測(cè)試中,Response.write 語(yǔ)句與變量聲明都移入一個(gè)子程序區(qū)中。
< % OPTION EXPLICIT
CALL writeTable()
SUB writeTable()
Dim FirstName
…
Dim BirthDate
FirstName = "John"
…
BirthDate = "1/1/1950"
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 >")
END SUB
/app2/function2.asp片段
基準(zhǔn)值= 5.57 msec/page
反應(yīng)時(shí)間= 5.22 msec/page
差 = -0.35 msec (6.3% 降低)
非常有趣!盡管將變量移到函數(shù)范圍內(nèi)增加了額外的函數(shù)調(diào)用,但實(shí)際上卻提高了性能。我們又可以增加以下規(guī)則:
* 在一個(gè)頁(yè)面上,如果代碼要使用一次以上,就將代碼封入函數(shù)區(qū)。
* 適當(dāng)時(shí)候,將變量聲明移到函數(shù)范圍內(nèi)。
使用包含文件有什么影響?
ASP編程的一個(gè)重要功能就是包含來(lái)自其它頁(yè)面的代碼。通過(guò)這項(xiàng)功能,程序員可以在多個(gè)頁(yè)面上共享函數(shù),使代碼更易于維護(hù)。缺點(diǎn)在于服務(wù)器必須從多個(gè)來(lái)源組裝頁(yè)面。以下是使用Include文件的兩個(gè)測(cè)試。
使用內(nèi)聯(lián)代碼的Include 文件
在這個(gè)測(cè)試中,有一小段代碼被移到一個(gè)Include 文件中:
< % OPTION EXPLICIT
Dim FirstName
…
Dim BirthDate
FirstName = "John"
…
BirthDate = "1/1/1950"
% >
< !-- #include file="inc1.asp" -- >
/app2/include_1.asp片段
基準(zhǔn)值 = 5.57 msec/page
反應(yīng)時(shí)間= 5.93 msec/page
差 = +0.36 msec (6.5% 增加)
這不奇怪。使用Include 文件形成了負(fù)載。
在函數(shù)區(qū)使用Include 文件
在這里,代碼都包裝在一個(gè)Include 文件中的子程序里。Include 引用是在頁(yè)面頂部進(jìn)行的,在ASP腳本的適當(dāng)位置調(diào)用子程序。
< % OPTION EXPLICIT
Dim FirstName
…
Dim BirthDate
FirstName = "John"
…
BirthDate = "1/1/1950"
CALL writeTable()
% >
< !-- #include file="inc2.asp" -- >
/app2/include_2.asp片段
基準(zhǔn)值 = 5.57 msec/page
反應(yīng)時(shí)間= 6.08 msec/page
差 =+0.51 msec (9.2% 增加)
這對(duì)性能造成的影響比f(wàn)unctions調(diào)用還大。因此:只有當(dāng)代碼在頁(yè)面之間共享時(shí)才使用Include 文件。
執(zhí)行錯(cuò)誤處理時(shí)會(huì)形成多大的負(fù)載?
對(duì)于所有真正的應(yīng)用程序來(lái)說(shuō),錯(cuò)誤處理都是必要的。這個(gè)測(cè)試中,通過(guò)調(diào)用On Error Resume Next函數(shù)來(lái)調(diào)用錯(cuò)誤句柄。
< % OPTION EXPLICIT
On Error Resume Next
Dim FirstName
…
/app2/error_1.asp片段
基準(zhǔn)值 = 5.57 msec/page
反應(yīng)時(shí)間= 5.67 msec/page
差= 0.10 msec (1.8% 增加)
你可以看到,錯(cuò)誤句柄帶來(lái)了代價(jià)。我們可以提出以下建議:只有在會(huì)發(fā)生超出測(cè)試或控制能力之外的情況時(shí)才使用錯(cuò)誤句柄。一個(gè)最基本的例子就是使用存取其它資源,如ADO或FileSystem 對(duì)象的COM對(duì)象。
設(shè)置一個(gè)上下文處理是否對(duì)性能有影響?
當(dāng)錯(cuò)誤發(fā)生時(shí),在頁(yè)面上設(shè)置一個(gè)上下文處理允許腳本進(jìn)行反轉(zhuǎn)操作。這是通過(guò)在頁(yè)面上使用處理聲明來(lái)設(shè)置的。
< %@ TRANSACTION = REQUIRED % >
< % OPTION EXPLICIT
Dim FirstName
…
/app2/transact1.asp片段
基準(zhǔn)值 = 5.57 msec/page
反應(yīng)時(shí)間= 13.39 msec/page
差 = +7.82 msec (140.4% 增加)
!這真實(shí)最具有戲劇性的結(jié)果。所以請(qǐng)留意以下規(guī)則:只有當(dāng)兩個(gè)或更多操作被作為一個(gè)單元執(zhí)行時(shí),才使用處理上下文。
結(jié)論
本文第一部分的重要之處在于許多小事情的累積。為了強(qiáng)調(diào)這個(gè)問(wèn)題,我設(shè)置了最后一個(gè)測(cè)試,在其中進(jìn)行了我們以前曾經(jīng)測(cè)試過(guò)的看來(lái)無(wú)所謂但實(shí)際上有壞影響的所有操作。我包含了許多Response.Write 聲明、關(guān)閉了緩沖器、設(shè)置了默認(rèn)語(yǔ)言、去掉了Option Explicit 引用并初始化了錯(cuò)誤句柄。
< %@ LANGUAGE=VBSCRIPT % >
< %
On Error Resume Next
FirstName = "John"
…
BirthDate = "1/1/1950"
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 >Birth Date:< /b >< /td >< td >" & BirthDate & "< /td >< /tr >")
Response.Write("< /table >")
Response.Write("< /body >")
Response.Write("< /html >")
% >
/app2/final_1.asp片段
基準(zhǔn)值 = 5.57 msec/page
反應(yīng)時(shí)間 = 8.85 msec/page
差 = +3.28 msec (58.9% 增加)
聽起來(lái)可能很明顯,但是理解更重要,那就是我們放置在頁(yè)面上的代碼會(huì)對(duì)性能有影響。頁(yè)面上的小變化有時(shí)會(huì)大大地增加反應(yīng)時(shí)間。
規(guī)則概括
* 避免內(nèi)聯(lián)ASP的過(guò)多使用。
* 總是將連續(xù)Response.Write 語(yǔ)句連接進(jìn)一個(gè)單獨(dú)語(yǔ)句內(nèi)。
* 永遠(yuǎn)不要在Response.Write 周圍使用包裝函數(shù)以附加CRLF。
* 如果必須格式化HTML輸出,直接在Response.Write 語(yǔ)句內(nèi)附加CRLF。
* 總是通過(guò)服務(wù)器設(shè)置開啟緩沖器。
* 只要使用適度,ASP注釋對(duì)性能的影響很小或根本沒(méi)有影響。
* 設(shè)置服務(wù)器的默認(rèn)語(yǔ)言配置以與站點(diǎn)上使用的語(yǔ)言相匹配。
* 除非你使用非默認(rèn)語(yǔ)言,不要設(shè)置語(yǔ)言聲明。
* 在VBScript中總是使用Option explicit 。
* 在不需要的情況下,總是在頁(yè)面或應(yīng)用程序的水平上關(guān)閉Session狀態(tài)。
* 只有當(dāng)代碼在頁(yè)面之間共享時(shí)才使用Include 文件。
* 在一個(gè)頁(yè)面上,如果代碼要使用一次以上,就將代碼封入函數(shù)區(qū)。
* 適當(dāng)時(shí)候,將變量聲明移到函數(shù)范圍內(nèi)。
* 只有會(huì)發(fā)生超出測(cè)試或控制能力之外的情況時(shí)才使用錯(cuò)誤句柄。
* 只有當(dāng)兩個(gè)或更多操作被作為一個(gè)單元執(zhí)行時(shí),才使用上下文處理。
現(xiàn)在回顧一下,有許多問(wèn)題可以作為普遍性的方針:
* 避免冗余--不要設(shè)置那些默認(rèn)狀態(tài)下已經(jīng)設(shè)置的屬性。
* 限制函數(shù)調(diào)用的次數(shù)。
* 縮小代碼的范圍。
在本文的第二部分,我們將探索有關(guān)ADO和COM對(duì)象一些深入的問(wèn)題。
在本文的第一部分中,我回顧了有關(guān)ASP開發(fā)的一些基本問(wèn)題,介紹了一些性能測(cè)試的結(jié)果,以理解我們放置在頁(yè)面中的代碼可能對(duì)運(yùn)行性能造成什么樣的影響。在這個(gè)系列的第二部分,我們將探討經(jīng)過(guò)論證的ASP最廣泛的用途,即通過(guò)ActiveX 數(shù)據(jù)對(duì)象(ADO)交互使用數(shù)據(jù)庫(kù)內(nèi)容。ADO是Microsoft通用并簡(jiǎn)單的數(shù)據(jù)庫(kù)界面。
ADO有很多的功能設(shè)置,因此準(zhǔn)備這篇文章時(shí)最大的挑戰(zhàn)便是限制測(cè)試問(wèn)題的范圍。考慮到讀取大數(shù)據(jù)集會(huì)為web 服務(wù)器施加很大的負(fù)載,我決定將研究的內(nèi)容局限在為使用ADO記錄集尋找最優(yōu)化配置的方面。但是這個(gè)限制還是提出了一個(gè)挑戰(zhàn),因?yàn)锳DO為執(zhí)行同一個(gè)功能提供了多種方式。比如說(shuō),記錄集可以從Recordset 類中恢復(fù),也可以從Connection和Command 類中恢復(fù)。另外,一旦你有了一個(gè)記錄集,那么有很多個(gè)選擇會(huì)戲劇性地影響性能。因此,同第一部分一樣,我將盡可能地多涉及一些具體問(wèn)題。
目的
我研究的目的是獲取足夠的信息以找到以下問(wèn)題的答案:
* 是否應(yīng)該使用ADOVBS.inc包含文件?
* 當(dāng)使用一個(gè)記錄集時(shí),是否應(yīng)該創(chuàng)建一個(gè)單獨(dú)的Connection對(duì)象?
* 恢復(fù)一個(gè)記錄集最好的方法是什么?
* 指針和鎖的類型中,哪些是最有效的?
* 是否應(yīng)該使用斷開的記錄集?
* 設(shè)置記錄集(Recordset)屬性的最好方法是什么?
* 引用記錄集中域值的最有效方法是什么?
* 使用臨時(shí)字符串可以較好地代替緩沖器嗎?
測(cè)試是如何設(shè)立的?
為進(jìn)行這項(xiàng)研究中的測(cè)試,我們共組裝了21個(gè)ASP頁(yè)面(包含在本文下載內(nèi)容中)。每個(gè)頁(yè)面都被配置成用3個(gè)不同的查詢返回記錄集運(yùn)行,這些記錄集中分別有0、25、250條記錄。這可以幫助我們將裝載記錄集的問(wèn)題和在記錄集中循環(huán)上的性能問(wèn)題隔離開。
為滿足這些變化的條件,數(shù)據(jù)庫(kù)連接字符串和測(cè)試SQL字符串都作為應(yīng)用程序變量存儲(chǔ)在Global.asa中。因?yàn)槲覀兊臏y(cè)試數(shù)據(jù)庫(kù)是在Microsoft SQL Server 7.0上運(yùn)行的,因此我們的連接字符串指定OLEDB作為連接供應(yīng)者、Northwind 樣本數(shù)據(jù)庫(kù)(包含在SQL服務(wù)器中)作為當(dāng)前數(shù)據(jù)庫(kù)。SQL SELECT語(yǔ)句要求Northwind Orders 表格中的7個(gè)特定域。
< SCRIPT LANGUAGE=VBScript RUNAT=Server >
Sub Application_OnStart
Application("Conn") = "Provider=SQLOLEDB; " & _
"Server=MyServer; " & _
"uid=sa; " & _
"pwd=;" & _
"DATABASE=northwind"
Application("SQL") = "SELECT TOP 0 OrderID, " & _
" CustomerID, " & _
" EmployeeID, " & _
" OrderDate, " & _
" RequiredDate, " & _
" ShippedDate, " & _
" Freight " & _
"FROM [Orders] "
End Sub
< /SCRIPT >
'alternate sql ?25 records
Application("SQL") = "SELECT TOP 25 OrderID, " & _
" CustomerID, " & _
" EmployeeID, " & _
" OrderDate, " & _
" RequiredDate, " & _
" ShippedDate, " & _
" Freight " & _
"FROM [Orders] "
'alternate sql ?250 records
Application("SQL") = "SELECT TOP 250 OrderID, " & _
" CustomerID, " & _
" EmployeeID, " & _
" OrderDate, " & _
" RequiredDate, " & _
" ShippedDate, " & _
" Freight " & _
"FROM [Orders] "
我們的測(cè)試服務(wù)器是一個(gè)雙450 MHz Pentium ,512MB的RAM,在其上運(yùn)行著NT Server 4.0 SP5, MDAC 2.1 (數(shù)據(jù)訪問(wèn)組件)以及Microsoft Scripting Engine的5.0版本。SQL服務(wù)器在一個(gè)同樣規(guī)格的單獨(dú)機(jī)器上運(yùn)行。同第一篇文章一樣,我使用Microsoft的Web應(yīng)用程序重點(diǎn)工具記錄從最初的頁(yè)面請(qǐng)求到傳輸最后一個(gè)字節(jié)(TTLB )的時(shí)間,精確到服務(wù)器上的毫秒級(jí)。這個(gè)測(cè)試腳本運(yùn)行20小時(shí),調(diào)用每個(gè)頁(yè)面1300次以上。顯示的時(shí)間是session的平均TTLB。要記住的是,同第一篇文章一樣,我們只是試圖涉及性能方面的問(wèn)題,而非伸縮性和容量的問(wèn)題。
還請(qǐng)注意,我們?cè)诜⻊?wù)器上開啟了緩沖器。另外,我把所有的文件名都定為同樣長(zhǎng)度,因此文件名中就會(huì)有一個(gè)或多個(gè)下劃線來(lái)襯墊。
開始
在第一個(gè)測(cè)試中,我們使用典型Microsoft ASP ADO 樣本文件中的典型場(chǎng)景來(lái)恢復(fù)一個(gè)簡(jiǎn)單的記錄集。在這個(gè)例子( ADO__01.asp )中,我們首先創(chuàng)建一個(gè)Connection對(duì)象,然后創(chuàng)建一個(gè)Recordset對(duì)象。當(dāng)然,我在腳本中進(jìn)行了一些修改,以反映在本系列的第一部分中涉及到的一些好的做法。
< % 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é)果是這樣的:
現(xiàn)在先來(lái)看看每一欄中的數(shù)字代表什么:
0 代表運(yùn)行返回0個(gè)記錄的查詢時(shí)的TTLB,單位毫秒。在我們所有測(cè)試中,這個(gè)數(shù)字用來(lái)標(biāo)志頁(yè)面的負(fù)載或裝載頁(yè)面創(chuàng)建對(duì)象但不在數(shù)據(jù)中循環(huán)所用的時(shí)間。
25 裝載并顯示25條記錄的TTLB(毫秒)。
tot time/25 TTLB除以25條記錄(毫秒)。代表每條記錄的總平均時(shí)間。
disp time/25 以毫秒計(jì)的TTLB減去“0”那欄的TTLB,并除以25條記錄。代表在記錄集中循環(huán)顯示每條記錄的時(shí)間。
250 裝載并顯示250條記錄的TTLB(毫秒)。
tot time/250 TTLB除以250條記錄(毫秒)。代表每條記錄的總平均時(shí)間。
disp time/250 以毫秒計(jì)的TTLB減去“0”那欄的TTLB,并除以250條記錄。代表在記錄集中循環(huán)顯示每條記錄的時(shí)間。
我們將用下面測(cè)試的結(jié)果與這些值相比較。
是否應(yīng)該使用ADOVBS.inc 包含文件?
這個(gè)問(wèn)題我想快點(diǎn)解決。Microsoft 提供的ADOVBS.inc 文件包含270行代碼,代表可以應(yīng)用于ADO屬性的大部分常量。我們的例子中只引用了這個(gè)文件中的2個(gè)常量。因此對(duì)于這個(gè)測(cè)試( ADO__02.asp ),我取消了包含文件的引用,并用屬性列舉中的實(shí)際數(shù)字代替了常量。
objRS.CursorType = 0 ' adOpenForwardOnly
objRS.LockType = 1 ' adLockReadOnly
我們可以看到裝載時(shí)間減少了23%。這與每條記錄的顯示時(shí)間有定義上的不同,因?yàn)檫@種改變對(duì)于在記錄集中循環(huán)不應(yīng)該有影響。這個(gè)問(wèn)題有幾種解決辦法。我建議使用ADOVBS.inc 文件作為參考,必要時(shí)使用注釋來(lái)注明數(shù)字。要記住,就如同在第一部分所闡明的一樣,注釋是不需要懼怕的,因?yàn)橹灰褂眠m度,它們不會(huì)給性能帶來(lái)大的影響。另一種方法是只從文件中將你所需要的常量復(fù)制到頁(yè)面中。
解決這個(gè)問(wèn)題有一個(gè)很酷的方法,通過(guò)將ADO類庫(kù)連接到你的應(yīng)用程序,使所有的ADO常量都可用。將以下代碼增加到你的Global.asa 文件,你就可以直接使用所有的常量。
< !--METADATA TYPE="typelib"
FILE="C:\Program Files\Common Files\SYSTEM\ADO\msado15.dll"
NAME="ADODB Type Library" -- >
或
< !--METADATA TYPE="typelib"
UUID="00000205-0000-0010-8000-00AA006D2EA4"
NAME="ADODB Type Library" -- >
所以,這里是我們的第一個(gè)規(guī)則:
* 避免包含ADOVBS.inc文件,用其它方法來(lái)使用常量。
當(dāng)使用一個(gè)記錄集時(shí),是否應(yīng)該創(chuàng)建一個(gè)單獨(dú)的Connection對(duì)象?
要想正確回答這個(gè)問(wèn)題,需要在兩個(gè)不同情況下檢驗(yàn)測(cè)試結(jié)果:第一是每頁(yè)執(zhí)行一個(gè)數(shù)據(jù)庫(kù)處理的情況,第二是每頁(yè)執(zhí)行多個(gè)數(shù)據(jù)庫(kù)處理的情況。
在前面的例子中,我們已經(jīng)創(chuàng)建了一個(gè)單獨(dú)的Connection對(duì)象,并將它傳遞到記錄集的ActiveConnection 屬性。但是也有可能僅僅把連接字符串傳遞到這個(gè)屬性中,從而可以避免一個(gè)額外的步驟,即在腳本( ADO__03.asp )中例示和配置一個(gè)單獨(dú)的組件:
objRS.ActiveConnection = Application("Conn")
盡管我們?nèi)匀辉谟涗浖袆?chuàng)建了一個(gè)連接,但它是在非常優(yōu)化的情況下創(chuàng)建的,所以剛一開始我們就看到啟動(dòng)時(shí)間比以前的測(cè)試減少了23%,同預(yù)料中一樣,同每個(gè)記錄的顯示時(shí)間幾乎沒(méi)有什么差別。
因此,我們的第二個(gè)規(guī)則是:
* 當(dāng)使用一個(gè)單個(gè)記錄集時(shí),將連接字符串傳遞到ActiveConnection屬性中。
下面要確定當(dāng)在一個(gè)頁(yè)面上創(chuàng)建多個(gè)記錄集時(shí),這個(gè)邏輯是否依然成立。為測(cè)試這個(gè)情況,我引入了FOR 循環(huán),將前面的例子重復(fù)10次。在這個(gè)測(cè)試中,我們還將研究3種選擇:
第一,我們?cè)诿總(gè)循環(huán)中創(chuàng)建并銷毀Connection 對(duì)象( ADO__04.asp ):
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
第二,在循環(huán)外創(chuàng)建一個(gè)單獨(dú)的Connection 對(duì)象,并與每個(gè)記錄集共享它( ADO__05.asp ):
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
第三,在每個(gè)循環(huán)中將連接字符串傳遞到ActiveConnection 屬性( ADO__06.asp ):
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
你可能已經(jīng)猜到了,在每個(gè)循環(huán)中創(chuàng)建并銷毀Connection 對(duì)象是一個(gè)低效率的方法。但是令人吃驚的是,僅僅在每個(gè)循環(huán)中傳遞連接字符串比共享單一連接對(duì)象的效率只低一點(diǎn)點(diǎn)。
盡管如此,我們的第3條規(guī)則是:
* 在一個(gè)頁(yè)面上使用多個(gè)記錄集時(shí),創(chuàng)建一個(gè)Connection 對(duì)象,在ActiveConnection 屬性中重復(fù)使用它。
指針和鎖的類型中,哪些是最有效的?
到目前為止,我們所有測(cè)試都只用了只向前(Forward Only )的指針在記錄集中循環(huán)。但是,ADO還為記錄集提供了3種類型的指針:Static(靜態(tài)), Dynamic(動(dòng)態(tài))和 Keyset(鍵盤)。每一種都提供了額外的功能,比如向前和向后移動(dòng)以及當(dāng)別人建立數(shù)據(jù)時(shí)可以看到修改情況的功能。不過(guò),討論這些指針類型的內(nèi)涵不是本文討論的范圍。我把這些留給你自己。下面是各種類型的比較分析。
與它們的同類Forward Only 相比,這些額外的指針都明顯地造成了更大的負(fù)載( ADO__03.asp )。另外這些指針在循環(huán)期間也更慢。我想與你一起分享的一條忠告是要避免這種想法:“我不時(shí)地需要一下Dynamic 指針,所以干脆總是用它算了!
從本質(zhì)上說(shuō),同樣的問(wèn)題也適用于鎖的類型。前面的測(cè)試中只使用了Read Only(只讀)類型的鎖。但是,還有三種類型的鎖:Lock Pessimistic、 Lock Optimistic和Lock Batch Optimistic。同指針的選擇一樣,這些鎖也為處理記錄集中的數(shù)據(jù)提供了額外的功能和控制。同樣,我將學(xué)習(xí)每種鎖設(shè)置的適當(dāng)用途的內(nèi)容留給你自己。
所以引導(dǎo)我們考慮規(guī)則4的邏輯很簡(jiǎn)單:使用最適合你的任務(wù)的最簡(jiǎn)單的指針和鎖的類型。
獲取一個(gè)記錄集最好的方式是什么?
到目前為止,我們只是通過(guò)Recordset 對(duì)象來(lái)恢復(fù)記錄集。但是ADO還提供了一些獲取記錄集的間接方法。下一個(gè)測(cè)試就將ADO__03.asp 中的值與直接從一個(gè)Connection對(duì)象中創(chuàng)建一個(gè)記錄集對(duì)象( CONN_01.asp )來(lái)比較。
Set objConn = Server.CreateObject("ADODB.Connection")
objConn.Open Application("Conn")
Set objRS = objConn.Execute(Application("SQL"))
我們看到,負(fù)載有一個(gè)輕微的增加,顯示每條記錄的時(shí)間沒(méi)有變化。
然后,我們看看從一個(gè)Command 對(duì)象中直接創(chuàng)建一個(gè)Recordset 對(duì)象( CMD__01.asp ):
Set objCmd = Server.CreateObject("ADODB.Command")
objCmd.ActiveConnection = Application("Conn")
objCmd.CommandText = Application("SQL")
Set objRS = objCmd.Execute
我們?cè)俅慰吹截?fù)載有一個(gè)輕微的增加,每個(gè)記錄的顯示時(shí)間有一個(gè)名義上的區(qū)別。雖然最后這兩種方法對(duì)性能的影響很小,卻有一個(gè)大問(wèn)題需要考慮。
通過(guò)Recordset 類創(chuàng)建一個(gè)記錄集對(duì)于控制如何處理記錄集提供了最大的靈活性。雖然其它方法也沒(méi)有提出一個(gè)壓倒性的性能問(wèn)題,但是你會(huì)被默認(rèn)狀態(tài)下返回何種指針類型和鎖類型而困惑,這些對(duì)于你的特定需求來(lái)說(shuō)不一定是最優(yōu)的。
所以,除非因?yàn)槟撤N特殊原因你需要其它方法的話,請(qǐng)遵循第5條規(guī)則:通過(guò)ADODB.Recordset 類例示記錄集以獲得最好的性能和最大的靈活性。
是否應(yīng)該斷開記錄集?
ADO為斷開一個(gè)記錄集提供了一種選擇,記錄集要在一個(gè)向前查詢中恢復(fù)所有數(shù)據(jù)、關(guān)閉連接、使用一個(gè)本地(或客戶)指針在數(shù)據(jù)集中移動(dòng)。這還提供了一個(gè)早期釋放連接的機(jī)會(huì)。這種情況對(duì)于處理遠(yuǎn)程數(shù)據(jù)服務(wù)是必要的,因?yàn)檫@種情況下數(shù)據(jù)必須從數(shù)據(jù)庫(kù)斷開。但是對(duì)于普通的用途,這樣做有好處嗎?
下面我們?cè)黾恿薈ursorLocation 屬性,打開記錄集后關(guān)閉連接( CLIENT1.asp ):
Set objRS = Server.CreateObject("ADODB.Recordset")
objRS.CursorLocation = 3 ' adUseClient
objRS.ActiveConnection = Application("Conn")
objRS.LockType = 1 ' adLockReadOnly
objRS.Open Application("SQL")
objRS.ActiveConnection = Nothing
從理論上說(shuō),這個(gè)技術(shù)應(yīng)該導(dǎo)致性能更快。原因有兩個(gè):首先,在記錄集中移動(dòng)時(shí),避免了通過(guò)連接的重復(fù)請(qǐng)求;其次通過(guò)較早地取消連接減輕了資源需求。但是,在使用客戶端指針時(shí),效率低得很明顯?赡苁怯捎诋(dāng)使用客戶指針位置時(shí),不管你的設(shè)置是什么,CursorType 都被修改成Static。
規(guī)則6是這樣的:除非是一個(gè)斷開的環(huán)境中所要求的,避免使用斷開的記錄集。
什么是設(shè)置記錄集屬性的最好方法?
前面所有的測(cè)試都是通過(guò)單獨(dú)的屬性設(shè)置來(lái)直接設(shè)置記錄集的屬性的。但是Recordset.Open 函數(shù)可以為我們所需要的全部屬性接收額外的參數(shù)。雖然對(duì)于每個(gè)屬性來(lái)說(shuō),單獨(dú)的代碼行易于閱讀和維護(hù),它們還是要分別執(zhí)行一個(gè)單獨(dú)函數(shù)調(diào)用,必須通過(guò)COM界面來(lái)集合( ADO__07.asp ):
Set objRS = Server.CreateObject("ADODB.Recordset")
objRS.Open Application("SQL"), Application("Conn"), 0, 1
' adForwardOnly, adLockReadOnly
這些方法在負(fù)載上帶來(lái)得差別小得驚人,于是我們得到規(guī)則7:不要對(duì)單獨(dú)設(shè)置記錄集屬性感到擔(dān)心
引用記錄集中域值的最有效方法是什么?
到目前為止,我都是用名字引用記錄集中的域值的。這可能是一種效率很低的方法,因?yàn)槊看握{(diào)用都需要查找域。為了證明這一點(diǎn),下面的測(cè)試就要通過(guò)記錄集中域的集合的指針來(lái)引用域(ADO__08.asp):
'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ù)料的,裝載時(shí)間的變化很。ú町惪赡苁怯捎诖a上的輕微減少引起的)。但是這種技術(shù)在有效顯示時(shí)間上卻帶來(lái)了明顯的減少。
在下面的例子中,我們將給每個(gè)域指定一個(gè)單獨(dú)的變量。這種方法避免了在表格循環(huán)內(nèi)的所有查找( ADO__09.asp ):
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
到目前,這種方法形成的結(jié)果是最好的。每條記錄的顯示時(shí)間下降成了.45 毫秒。
現(xiàn)在,所有測(cè)試腳本的配置都要求對(duì)結(jié)果記錄集有一些了解。比如說(shuō),我們一直在欄標(biāo)題中給域名編碼,單獨(dú)地引用這些域的值。下面的例子提供了一個(gè)動(dòng)態(tài)的解決方案,在域的集合中循環(huán),不僅得到數(shù)據(jù),也得到域的標(biāo)題(ADO__10.asp ):
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
可以看到,我們?cè)谛阅苌嫌幸粋(gè)損失,但是這個(gè)方法還是比ADO__07.asp要快一些。
下面的測(cè)試是在最后兩個(gè)測(cè)試之間進(jìn)行一些折中。通過(guò)在一個(gè)動(dòng)態(tài)分配數(shù)組中保存域的引用,既維持了動(dòng)態(tài)的靈活性,也挽回了一些性能上的損失。
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
雖然它并不比最好值快,但是比前面的幾個(gè)例子要快了很多,并且有一個(gè)優(yōu)勢(shì)就是能夠動(dòng)態(tài)地表現(xiàn)任何記錄集。
在下一個(gè)測(cè)試中,我們將對(duì)以前的方案做一個(gè)徹底的改變,使用記錄集的GetRows指令創(chuàng)建一個(gè)循環(huán)用的數(shù)組,而不是在記錄集本身進(jìn)行循環(huán)。注意,調(diào)用GetRows之后,立刻就將記錄集設(shè)置為Nothing,這樣就能更快地釋放系統(tǒng)資源。另外還要注意數(shù)組的第一個(gè)維數(shù)代表域,第二個(gè)維數(shù)代表行 ( ADO__12.asp ):
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
通過(guò)使用GetRows 指令,就可以獲取整個(gè)記錄集并將其裝載到數(shù)組中。當(dāng)恢復(fù)特別大的記錄集時(shí),這種方法有可能會(huì)造成資源問(wèn)題,但是數(shù)據(jù)的循環(huán)快多了,因?yàn)轭愃朴贛oveNext 的函數(shù)調(diào)用和EOF 的檢測(cè)都可以取消了。
不過(guò)速度的提升確實(shí)是有代價(jià)的,因?yàn)橛涗浖脑獢?shù)據(jù)不再與數(shù)據(jù)在一起。圍繞這個(gè)問(wèn)題,我在調(diào)用GetRows之前用記錄集來(lái)恢復(fù)標(biāo)題名。另外還可以提前提取數(shù)據(jù)類型和其它信息。還要注意,在我們的測(cè)試中,性能上的優(yōu)勢(shì)只有在使用大一些的記錄集時(shí)才能看到。
在這部分最后的測(cè)試中,我們更進(jìn)一步,使用記錄集的GetString 指令。這個(gè)方法將整個(gè)記錄集提取到一個(gè)大的字符串中,允許你指定自己的分隔符( ADO__13.asp ):
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
雖然這種方法已經(jīng)接近了最高水平,但是它只適合于最簡(jiǎn)單的設(shè)計(jì),因?yàn)樗揪筒荒軕?yīng)用于數(shù)據(jù)的特殊情況。
觀察
在我們開始這套測(cè)試之前,執(zhí)行每條記錄的時(shí)間一直在.83 毫秒左右震動(dòng)。這套測(cè)試中的大多數(shù)方法都將這個(gè)數(shù)字減少了一半。雖然有些方法明顯地提供了更快的速度,但是代價(jià)是靈活性的降低。
下面的規(guī)則是以重要程度為順序的:
* 當(dāng)記錄集中的值不需要用一種特殊方式來(lái)對(duì)待并且能夠格式化為一種統(tǒng)一的格式時(shí),使用GetString方法來(lái)提取數(shù)據(jù)。
* 當(dāng)你在設(shè)計(jì)上需要更大的靈活性,但是又不需要用記錄集的元數(shù)據(jù)進(jìn)行工作,使用GetRows 方法將數(shù)據(jù)提取到一個(gè)數(shù)組中。
* 當(dāng)你需要設(shè)計(jì)的靈活性和元數(shù)據(jù)時(shí),在進(jìn)入一個(gè)數(shù)據(jù)恢復(fù)的循環(huán)之前,將你的域約束在本地變量中。避免用名字引用域。
使用臨時(shí)字符串可以較好地代替緩沖器嗎?
這是針對(duì)我上一篇文章提交的一些注解所引發(fā)的一個(gè)小小的離題。要討論的問(wèn)題是圍繞著緩沖器的使用及使用臨時(shí)字符串作為替代來(lái)收集輸出,這樣就允許Response.Write 只調(diào)用一次。為了測(cè)試,我從ADO_11.asp的代碼開始,將結(jié)果附加到一個(gè)字符串中,而不是在每個(gè)循環(huán)都調(diào)用Response.Write,當(dāng)整個(gè)操作都結(jié)束后,在字符串上調(diào)用Response.Write ( STR__01.asp ):
Dim strTable
strTable = ""
'write headings
strTable = strTable & "< TABLE BORDER=1 >< TR >"
For i = 0 to fldCount-1
strTable = strTable & "< TH >" & fld(i).name & "< /TH >"
Next
strTable = strTable & "< /TR >"
'write data
Do While Not objRS.EOF
strTable = strTable & "< TR >"
For i = 0 to fldCount-1
strTable = strTable & "< TD >" & fld(i) & "< /TD >"
Next
strTable = strTable & "< /TR >"
objRS.MoveNext
Loop
For i = 0 to fldCount-1
Set fld(i) = Nothing
Next
strTable = strTable & "< /TABLE >"
Response.Write(strTable)
看起來(lái)執(zhí)行得不是很好。也許正象許多人建議的,我們應(yīng)該用Space 指令為這個(gè)字符串指定一些空間,這樣它就不需要在循環(huán)期間總是為自己重新分配空間( STR__02.asp ):
Dim strTable
strTable = Space(10000)
也許Space 指令并不象建議的那樣工作。我們最后的規(guī)則是:不要用臨時(shí)字符串來(lái)收集輸出。
規(guī)則的總結(jié)
現(xiàn)在我們來(lái)重新總結(jié)一下這些規(guī)則:
* 避免包含ADOVBS.inc文件,用其它方法來(lái)使用常量。
* 當(dāng)使用一個(gè)單個(gè)記錄集時(shí),將連接字符串傳遞到ActiveConnection屬性中。
* 在一個(gè)頁(yè)面上使用多個(gè)記錄集時(shí),創(chuàng)建一個(gè)Connection 對(duì)象,在ActiveConnection 屬性中重復(fù)使用它。
* 使用最適合你的任務(wù)的最簡(jiǎn)單的指針和鎖的類型。
* 通過(guò)ADODB.Recordset 類例示記錄集以獲得最好的性能和最大的靈活性。
* 除非是一個(gè)斷開的環(huán)境中所要求的,避免使用斷開的記錄集。
* 不要對(duì)單獨(dú)設(shè)置記錄集屬性感到擔(dān)心。
* 當(dāng)記錄集中的值不需要用一種特殊方式來(lái)對(duì)待并且能夠格式化為一種統(tǒng)一的格式時(shí),使用GetString方法來(lái)提取數(shù)據(jù)。
* 當(dāng)你在設(shè)計(jì)上需要更大的靈活性,但是又不需要用記錄集的元數(shù)據(jù)進(jìn)行工作,使用GetRows方法將數(shù)據(jù)提取到一個(gè)數(shù)組中。
* 當(dāng)你需要設(shè)計(jì)的靈活性和元數(shù)據(jù)時(shí),在進(jìn)入一個(gè)數(shù)據(jù)恢復(fù)的循環(huán)之前,將你的域約束在本地變量中。避免用名字引用域。
* 不要用臨時(shí)字符串來(lái)收集輸出。
結(jié)論
同樣,從這些測(cè)試中我們所學(xué)到的最重要的一點(diǎn)是:小小的變化會(huì)在性能上造成很大的影響。如果我們把第一個(gè)測(cè)試與ADO__09.asp(在記錄集中循環(huán)的最快結(jié)果)相比,可以看到在反應(yīng)時(shí)?br>
所以要記住,永遠(yuǎn)不要想當(dāng)然。如果你不能肯定,那就運(yùn)行一些有針對(duì)性的測(cè)試。