當前位置:開發者網絡 >> 技術教程 >> ASP教程 >> 性能優化 >> 內容
精彩推薦
分類最新教程
分類熱點教程
    
提高ASP性能的最佳選擇
作者:未知
日期:2004-03-09
人氣:
投稿:Andy.m(轉貼)
來源:未知
字體:
收藏:加入瀏覽器收藏
以下正文:
ASP開發人員為了在他們的設計項目中獲得更好的性能和可擴展性而不斷努力。幸運地是,有許多書籍和站點在這方面提供了很好的建議。但是這些建議的基礎都是從ASP平台工作的結構上所得出的結論,對實際獲得的性能的提高沒有量的測量。由於這些建議需要更加複雜的編碼過程並降低了編碼的可讀性,開發人員就只能在看不到實際運行效果的情況下,獨自衡量為了提高他們ASP應用程序的性能是否值得付出這些代價。

  本文分為兩大部分,我將介紹一些性能測試結果,幫助開發人員來確定某一特定舉措是否不僅對將來的項目來說是值得的,並且能夠對原來的項目進行更新。在第一部分我將回顧一些ASP開發的基礎性問題。在第二部分,將涉及一些最優化ADO函數,並將它們的結果與調用VB COM對像執行相同ADO函數的ASP頁面進行比較。這些結果很讓人開眼界,甚至有些時候是很令人吃驚的。

  在本文中,我們將回答以下問題:

  * 將ASP生成的內容寫入響應流中最有效的方法是什麼?

  * 是否應該開啟緩衝器?

  * 是否應該考慮向ASP代碼中增加註釋?

  * 是否應該為頁面明確地設置默認語言?

  * 如果不需要,是否應該關閉Session 狀態?

  * 是否應該把腳本邏輯放在子程序和函數區中?

  * 使用包含文件有什麼影響?

  * 執行錯誤處理時會施加什麼樣的負載?

  * 設置一個上下文處理是否對性能有影響?

所有測試都是用Microsoft的Web應用程序重點工具(WAST)來進行的,這是一個免費的工具,可以在這裡找到。我用WAST創建了一個簡單的test 腳本,反覆調用下面所描述的ASP頁面測試(每個超過70,000次)。反應的時間基於平均最後字節總時間(TTLB), 也就是從最初請求的時間到工具從服務器接收最後一位數據的時間。我們的測試服務器是一個Pentium 166,內存為196MB,客戶機為Pentium 450,內存為256MB。你也許會想這些機器的性能並不算很高級,但是不要忘了,我們並不是要測試服務器的容量,我們只是要測試服務器每次處理一個頁面所用的時間。測試期間這些機器不做其它工作。WAST 測試腳本、測試報告以及所有的ASP測試頁面都包含在ZIP文件中,你可以自己進行回顧和測試。
將ASP生成的內容寫入響應流中最有效的方法是什麼?
  使用ASP的一個最主要原因是在服務器上生成動態內容。所以很明顯,我們測試的起點是確定將動態內容發送到響應流中的最適合的方式。在多種選擇中,有兩個是最基本的:一是使用內聯ASP標記,另一個是使用Response.Write 語句。

  為測試這些選擇,我們創建了一個簡單的ASP頁面,其中定義了一些變量,然後將它們的值插入表格中。雖然這個頁面很簡單也不是很實用,但它允許我們分離並測試一些單獨的問題。

  使用ASP內聯標記

  第一個測試包括使用內聯ASP標記< %= x % >,其中x是一個已賦值的變量。到目前為止,這個方法是最容易執行的,並且它使頁面的HTML部分保持一種易於閱讀和維護的格式。

  < % 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 msec/page

  在HTML的每一行使用Response.Write 語句

  許多比較好的學習文檔建議避免使用前面的那種方法。其主要理由是,在輸出頁面和處理頁面施加反應時間的過程中,如果web 服務器不得不在發送純HTML和處理腳本之間進行轉換,就會發生一種被稱為上下文轉換的問題。大部分程序員一聽到這裡,他們的第一反應就是將原始的HTML的每一行都包裝在Response.Write函數中。

  …

  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 msec/page

  反應時間 = 8.08 msec/page

  差= -0.20 msec (減少 2.4%)

  我們可以看到,使用這種方法與使用內聯標記的方法相比在性能上獲得的收益非常小,這也許是因為頁面給服務器裝載了一大堆小的函數調用。這種方法最大的缺點是,由於現在HTML都嵌入腳本中,所以腳本代碼變得更加冗長,更加難以閱讀和維護。

  使用包裝函數

  當我們試圖使用Response.Write 語句這種方法時,最令人灰心的發現可能就是Response.Write 函數不能在每行的結尾處放置一個CRLF 。因此,當你從瀏覽器中閱讀源代碼時,本來佈置得非常好的HTML,現在成了沒有結束的一行。我想,你的下一個發現可能會更令你恐怖:在Response 對像中沒有其姊妹函數Writeln 。所以,一個很明顯的反應就是為Response.Write 函數創建一個包裝函數,以便給每一行都附加一個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 msec/page

  反應時間= 10.11 msec/page

  差 = +2.03 msec (增加 25.1%)

  當然,由於這種方法有效地使函數調用次數加倍,其對性能的影響也很明顯,因此要不惜一切代價避免。具有諷刺意味的是CRLF也向反應流中為每行增加了2個字節,而這是瀏覽器不需要呈現到頁面上的。格式化良好的HTML所做的一切就是讓你的競爭者更容易閱讀你的HTML源代碼並理解你的設計。

  將連續的Response.Write 連接到一個單獨語句中

  不考慮我們前面用包裝函數進行的測試,下一個合乎邏輯的步驟就是從單獨的Response.Write 語句中提取出所有的字符串,將它們連接到一個單獨語句中,這樣就減少了函數調用的次數,極大地提高了頁面的性能。

  …

  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 msec/page

  反應時間 = 7.05 msec/page

  差 = -1.03 msec (減少12.7%)

  目前,這是最優化的配置。

  將連續的Response.Write 連接到一個單獨語句中,在每行結尾處增加一個CRLF

  考慮到那些要求他們的源代碼從瀏覽器中看要很純粹的人,我用vbCRLF 常量在前面測試中每行的結尾處插入了一些回車,然後重新運行。  

  …

  Response.Write("< html >" & vbCRLF & _

  "< head >" & vbCRLF & _

  " < title >Response Test< /title >" & vbCRLF & _

  "< /head >" & vbCRLF & _

  …

  /app1/response5.asp的片段

  前面的最佳(反應速度)= 7.05 msec/page

  反應時間= 7.63 msec/page

  差 = +0.58 msec (增加 8.5%)

  運行的結果在性能上有一點降低,這也許是由於額外的串聯和增加的字符量。

  回顧和觀測

  從前面有關ASP輸出的測試中可以得出一些規則:

  * 避免內聯ASP的過多使用。

  * 總是將連續Response.Write 語句連接進一個單獨語句內。

  * 永遠不要在Response.Write 周圍使用包裝函數來附加CRLF。

  * 如果必須格式化HTML輸出,直接在Response.Write 語句內附加CRLF。

是否應該開啟緩衝器?
  通過腳本程序啟動緩衝器

  在ASP腳本的頂部包含Response.Buffer=True ,IIS就會將頁面的內容緩存。

  < % OPTION EXPLICIT

  Response.Buffer = true

  Dim FirstName

  …

  /app1/buffer__1.asp的片段

  以前的最佳(反應時間)= 7.05 msec/page

  反應時間 = 6.08 msec/page

  差= -0.97 msec (降低13.7%)

  性能得到了極大提高。但是等等,還能有更好的。

  通過服務器配置啟動緩衝器

  雖然在IIS 5.0中緩衝器是被默認啟動的,但是在IIS 4.0中還必須手動來啟動它。這時要找到站點的Properties 對話框,在那裡,從Home Directory 標籤中選擇配置按鈕。然後在"App options"下選擇"enable buffering" 。對於這個測試,Response.Buffer 語句從腳本中被移走了。

  以前的最佳= 7.05 msec/page

  反應時間 = 5.57 msec/page

  差= -1.48 msec (降低 21.0%)

  目前,這是我們所得到的最快反應了,比我們以前最好情況下的反應時間還要降低21%。從現在開始,我們以後的測試都要把這個反應時間作為基準值。

  回顧及觀測

  緩衝器是提高性能的好方法,所以把緩衝器設置成服務器的默認值很有必要。如果因為某些原因,頁面不能正確地使緩衝器運行,只需要Response.Buffer=False 命令即可。緩衝器的一個缺點是在整個頁面處理完之前,用戶從服務器看不到任何東西。因此,在複雜頁面的處理期間,偶而調用一次Response.Flush 來更新用戶是個好主意。

  現在在我們的規則中又增加了一條:總是通過服務器設置開啟緩衝器。

是否應該考慮向ASP代碼中增加註釋?
  大部分HTML開發人員都知道包含HTML註釋不是個好主意,首先會增加傳輸數據的規模,其次它們只是向別的開發人員提供有關你頁面組織的信息。但是ASP頁面上的註釋又如何呢?它們從來不離開服務器,但也確實要增加頁面的規模,因此必須用ASP進行分解。

  在這次的測試中,我們增加20條註釋,每條有80個字符,總共有1600個字符。

  < % OPTION EXPLICIT

  '-------------------------------------------------------------------------------

  … 20 lines …

  '-------------------------------------------------------------------------------

  Dim FirstName

  …

  /app2/comment_1.asp片段

  基準= 5.57 msec/page

  反應時間= 5.58 msec/page

  差 = +0.01 msec (增加 0.1%)

  測試的結果是驚人的。雖然註釋幾乎相當於文件本身的兩倍,但是它們的存在並沒有給反應時間帶來很大的影響。所以說我們可以遵循以下規則:

  只要使用適度,ASP註釋對性能的影響很小或根本沒有影響。

是否應該為頁面明確地設置默認語言?
  IIS處理VBScript是默認的設置,但是我看到,在大多數例子中還是用< %@LANGUAGE=VBSCRIPT% >聲明將語言明確地設置為VBScript 。我們的下一個測試將檢驗這個聲明的存在對性能有什麼影響。

  < %@ LANGUAGE=VBSCRIPT % >

  < % OPTION EXPLICIT

  Dim FirstName

  …

  /app2/language1.asp片段。

  基準值= 5.57 msec/page

  反應時間= 5.64 msec/page

  差= +0.07 msec (增加1.2%)

  可以看到,包含了語言的聲明對性能有一個輕微的影響。因此:

  * 設置服務器的默認語言配置以與站點上使用的語言相匹配。

  * 除非你使用非默認語言,不要設置語言聲明。

如果不需要,是否應該關閉Session 狀態?
  避免使用IIS的Session上下文有許多理由,那些已經可以獨立成為一篇文章。我們現在試圖回答的問題是當頁面不需要時,關閉Session上下文是否對性能提高有所幫助。從理論上講應該是肯定的,因為這樣一來就不需要用頁面例示Session上下文了。

  同緩衝器一樣,Session狀態也有兩種配置方法:通過腳本和通過服務器設置。

  通過腳本關閉Session上下文

  對於這個測試,要關閉頁面中的Session上下文,我增加一個Session狀態聲明。

  < %@ ENABLESESSIONSTATE = FALSE % >

  < % OPTION EXPLICIT

  Dim FirstName

  …

  /app2/session_1.asp片段。

  基準值= 5.57 msec/page

  反應時間= 5.46 msec/page

  差= -0.11 msec (降低2.0%)

  只通過這樣一個小小的努力就得到了不錯的進步。現在看看第二部分。

  通過服務器配置關閉Session 上下文

  要在服務器上關閉Session 上下文,請到站點的Properties 對話框。在Home Directory 標籤上選擇Configuration 按鈕。然後在"App options"下取消"enable session state" 的選擇。我們在沒有ENABLESESSIONSTATE 聲明的情況下運行測試。

  基準值 = 5.57 msec/page

  反應時間= 5.14 msec/page

  差= -0.43 msec (降低7.7%)

  這是性能的又一個顯著提高。所以,我們的規則應是:在不需要的情況下,總是在頁面或應用程序的水平上關閉Session狀態。

使用Option Explicit 會使性能有實質改變嗎?
  在一個ASP頁面的頂部設置Option Explicit 以要求所有的變量在使用之前都要在頁面上進行聲明。這有兩個原因。首先應用程序可以更快地處理變量的存取。其次,這樣可以防止我們無意中錯用變量的名字。在這個測試中我們移走Option Explicit 引用和變量的Dim 聲明。

  基準值 = 5.57 msec/page

  反應時間= 6.12 msec/page

  差 = +0.55 msec (9.8% 增加)、

  儘管有一些代碼行從頁面中去掉了,反應時間卻依然增加了。所以儘管使用Option explicit 有時候費時間,但是在性能上卻有很顯著的效果。因此我們又可以增加一條規則:在VBScript中總是使用Option explicit。

是否應該把腳本邏輯放在子程序和函數區?
  用函數和子程序來組織和管理代碼是一個很好的方法,特別是當一個代碼區在頁面中多次使用的情況。缺點是要在系統上增加一個做相同工作的額外函數調用。子程序和函數的另一個問題是變量的範圍。從理論上說,在一個函數區內指定變量更有效。現在我們看看這兩個方面如何發生作用。

  將Response.Write 語句移入子程序

  這個測試只是將Response.Write 語句移入一個子程序區內。

  …

  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片段

  基準值= 5.57 msec/page

  反應時間= 6.02 msec/page

  差 = +0.45 msec (8.1% 增加)

  同預料中一樣,子程序調用給頁面帶來了額外的負擔。

  將所有腳本移入子程序中

  在這個測試中,Response.write 語句與變量聲明都移入一個子程序區中。

  < % 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片段

  基準值= 5.57 msec/page

  反應時間= 5.22 msec/page

  差 = -0.35 msec (6.3% 降低)

  非常有趣!儘管將變量移到函數範圍內增加了額外的函數調用,但實際上卻提高了性能。我們又可以增加以下規則:

  * 在一個頁面上,如果代碼要使用一次以上,就將代碼封入函數區。

  * 適當時候,將變量聲明移到函數範圍內。

使用包含文件有什麼影響?
  ASP編程的一個重要功能就是包含來自其它頁面的代碼。通過這項功能,程序員可以在多個頁面上共享函數,使代碼更易於維護。缺點在於服務器必須從多個來源組裝頁面。以下是使用Include文件的兩個測試。

  使用內聯代碼的Include 文件

  在這個測試中,有一小段代碼被移到一個Include 文件中:

  < % OPTION EXPLICIT

  Dim FirstName

  …

  Dim BirthDate

  FirstName = "John"

  …

  BirthDate = "1/1/1950"

  % >

  < !-- #include file="inc1.asp" -- >

  /app2/include_1.asp片段

  基準值 = 5.57 msec/page

  反應時間= 5.93 msec/page

  差 = +0.36 msec (6.5% 增加)

  這不奇怪。使用Include 文件形成了負載。

  在函數區使用Include 文件

  在這裡,代碼都包裝在一個Include 文件中的子程序裡。Include 引用是在頁面頂部進行的,在ASP腳本的適當位置調用子程序。

  < % OPTION EXPLICIT

  Dim FirstName

  …

  Dim BirthDate

  FirstName = "John"

  …

  BirthDate = "1/1/1950"

  CALL writeTable()

  % >

  < !-- #include file="inc2.asp" -- >

  /app2/include_2.asp片段

  基準值 = 5.57 msec/page

  反應時間= 6.08 msec/page

  差 =+0.51 msec (9.2% 增加)

  這對性能造成的影響比functions調用還大。因此:只有當代碼在頁面之間共享時才使用Include 文件。

執行錯誤處理時會形成多大的負載?
  對於所有真正的應用程序來說,錯誤處理都是必要的。這個測試中,通過調用On Error Resume Next函數來調用錯誤句柄。

  < % OPTION EXPLICIT

  On Error Resume Next

  Dim FirstName

  …

  /app2/error_1.asp片段

  基準值 = 5.57 msec/page

  反應時間= 5.67 msec/page

  差= 0.10 msec (1.8% 增加)

  你可以看到,錯誤句柄帶來了代價。我們可以提出以下建議:只有在會發生超出測試或控制能力之外的情況時才使用錯誤句柄。一個最基本的例子就是使用存取其它資源,如ADO或FileSystem 對象的COM對象。

設置一個上下文處理是否對性能有影響?
  當錯誤發生時,在頁面上設置一個上下文處理允許腳本進行反轉操作。這是通過在頁面上使用處理聲明來設置的。

  < %@ TRANSACTION = REQUIRED % >

  < % OPTION EXPLICIT

  Dim FirstName

  …

  /app2/transact1.asp片段

  基準值 = 5.57 msec/page

  反應時間= 13.39 msec/page

  差 = +7.82 msec (140.4% 增加)

  啊!這真實最具有戲劇性的結果。所以請留意以下規則:只有當兩個或更多操作被作為一個單元執行時,才使用處理上下文。


結論
  本文第一部分的重要之處在於許多小事情的累積。為了強調這個問題,我設置了最後一個測試,在其中進行了我們以前曾經測試過的看來無所謂但實際上有壞影響的所有操作。我包含了許多Response.Write 聲明、關閉了緩衝器、設置了默認語言、去掉了Option Explicit 引用並初始化了錯誤句柄。

  < %@ 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片段

  基準值 = 5.57 msec/page

  反應時間 = 8.85 msec/page

  差 = +3.28 msec (58.9% 增加)

  聽起來可能很明顯,但是理解更重要,那就是我們放置在頁面上的代碼會對性能有影響。頁面上的小變化有時會大大地增加反應時間。

規則概括
  * 避免內聯ASP的過多使用。

  * 總是將連續Response.Write 語句連接進一個單獨語句內。

  * 永遠不要在Response.Write 周圍使用包裝函數以附加CRLF。

  * 如果必須格式化HTML輸出,直接在Response.Write 語句內附加CRLF。

  * 總是通過服務器設置開啟緩衝器。

  * 只要使用適度,ASP註釋對性能的影響很小或根本沒有影響。

  * 設置服務器的默認語言配置以與站點上使用的語言相匹配。

  * 除非你使用非默認語言,不要設置語言聲明。

  * 在VBScript中總是使用Option explicit 。

  * 在不需要的情況下,總是在頁面或應用程序的水平上關閉Session狀態。

  * 只有當代碼在頁面之間共享時才使用Include 文件。

  * 在一個頁面上,如果代碼要使用一次以上,就將代碼封入函數區。

  * 適當時候,將變量聲明移到函數範圍內。

  * 只有會發生超出測試或控制能力之外的情況時才使用錯誤句柄。

  * 只有當兩個或更多操作被作為一個單元執行時,才使用上下文處理。

  現在回顧一下,有許多問題可以作為普遍性的方針:

  * 避免冗余--不要設置那些默認狀態下已經設置的屬性。

  * 限制函數調用的次數。

  * 縮小代碼的範圍。

  在本文的第二部分,我們將探索有關ADO和COM對像一些深入的問題。


  在本文的第一部分中,我回顧了有關ASP開發的一些基本問題,介紹了一些性能測試的結果,以理解我們放置在頁面中的代碼可能對運行性能造成什麼樣的影響。在這個系列的第二部分,我們將探討經過論證的ASP最廣泛的用途,即通過ActiveX 數據對像(ADO)交互使用數據庫內容。ADO是Microsoft通用並簡單的數據庫界面。

  ADO有很多的功能設置,因此準備這篇文章時最大的挑戰便是限制測試問題的範圍。考慮到讀取大數據集會為web 服務器施加很大的負載,我決定將研究的內容局限在為使用ADO記錄集尋找最優化配置的方面。但是這個限制還是提出了一個挑戰,因為ADO為執行同一個功能提供了多種方式。比如說,記錄集可以從Recordset 類中恢復,也可以從Connection和Command 類中恢復。另外,一旦你有了一個記錄集,那麼有很多個選擇會戲劇性地影響性能。因此,同第一部分一樣,我將盡可能地多涉及一些具體問題。

目的
  我研究的目的是獲取足夠的信息以找到以下問題的答案:

  * 是否應該使用ADOVBS.inc包含文件?

  * 當使用一個記錄集時,是否應該創建一個單獨的Connection對像?

  * 恢復一個記錄集最好的方法是什麼?

  * 指針和鎖的類型中,哪些是最有效的?

  * 是否應該使用斷開的記錄集?

  * 設置記錄集(Recordset)屬性的最好方法是什麼?

  * 引用記錄集中域值的最有效方法是什麼?

  * 使用臨時字符串可以較好地代替緩衝器嗎?

測試是如何設立的?
  為進行這項研究中的測試,我們共組裝了21個ASP頁面(包含在本文下載內容中)。每個頁面都被配置成用3個不同的查詢返回記錄集運行,這些記錄集中分別有0、25、250條記錄。這可以幫助我們將裝載記錄集的問題和在記錄集中循環上的性能問題隔離開。

  為滿足這些變化的條件,數據庫連接字符串和測試SQL字符串都作為應用程序變量存儲在Global.asa中。因為我們的測試數據庫是在Microsoft SQL Server 7.0上運行的,因此我們的連接字符串指定OLEDB作為連接供應者、Northwind 樣本數據庫(包含在SQL服務器中)作為當前數據庫。SQL SELECT語句要求Northwind Orders 表格中的7個特定域。

  < 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] "

  我們的測試服務器是一個雙450 MHz Pentium ,512MB的RAM,在其上運行著NT Server 4.0 SP5, MDAC 2.1 (數據訪問組件)以及Microsoft Scripting Engine的5.0版本。SQL服務器在一個同樣規格的單獨機器上運行。同第一篇文章一樣,我使用Microsoft的Web應用程序重點工具記錄從最初的頁面請求到傳輸最後一個字節(TTLB )的時間,精確到服務器上的毫秒級。這個測試腳本運行20小時,調用每個頁面1300次以上。顯示的時間是session的平均TTLB。要記住的是,同第一篇文章一樣,我們只是試圖涉及性能方面的問題,而非伸縮性和容量的問題。

  還請注意,我們在服務器上開啟了緩衝器。另外,我把所有的文件名都定為同樣長度,因此文件名中就會有一個或多個下劃線來襯墊。

開始
  在第一個測試中,我們使用典型Microsoft ASP ADO 樣本文件中的典型場景來恢復一個簡單的記錄集。在這個例子( ADO__01.asp )中,我們首先創建一個Connection對象,然後創建一個Recordset對象。當然,我在腳本中進行了一些修改,以反映在本系列的第一部分中涉及到的一些好的做法。

  < % 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 >")

  % >

  結果是這樣的:


  現在先來看看每一欄中的數字代表什麼:

  0 代表運行返回0個記錄的查詢時的TTLB,單位毫秒。在我們所有測試中,這個數字用來標誌頁面的負載或裝載頁面創建對像但不在數據中循環所用的時間。

  25 裝載並顯示25條記錄的TTLB(毫秒)。

  tot time/25 TTLB除以25條記錄(毫秒)。代表每條記錄的總平均時間。

  disp time/25 以毫秒計的TTLB減去「0」那欄的TTLB,並除以25條記錄。代表在記錄集中循環顯示每條記錄的時間。

  250 裝載並顯示250條記錄的TTLB(毫秒)。

  tot time/250 TTLB除以250條記錄(毫秒)。代表每條記錄的總平均時間。

  disp time/250 以毫秒計的TTLB減去「0」那欄的TTLB,並除以250條記錄。代表在記錄集中循環顯示每條記錄的時間。

  我們將用下面測試的結果與這些值相比較。

是否應該使用ADOVBS.inc 包含文件?
  這個問題我想快點解決。Microsoft 提供的ADOVBS.inc 文件包含270行代碼,代表可以應用於ADO屬性的大部分常量。我們的例子中只引用了這個文件中的2個常量。因此對於這個測試( ADO__02.asp ),我取消了包含文件的引用,並用屬性列舉中的實際數字代替了常量。

  objRS.CursorType = 0 ' adOpenForwardOnly

  objRS.LockType = 1 ' adLockReadOnly


  我們可以看到裝載時間減少了23%。這與每條記錄的顯示時間有定義上的不同,因為這種改變對於在記錄集中循環不應該有影響。這個問題有幾種解決辦法。我建議使用ADOVBS.inc 文件作為參考,必要時使用註釋來註明數字。要記住,就如同在第一部分所闡明的一樣,註釋是不需要懼怕的,因為只要使用適度,它們不會給性能帶來大的影響。另一種方法是只從文件中將你所需要的常量複製到頁面中。

  解決這個問題有一個很酷的方法,通過將ADO類庫連接到你的應用程序,使所有的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" -- >

  所以,這裡是我們的第一個規則:

  * 避免包含ADOVBS.inc文件,用其它方法來使用常量。




當使用一個記錄集時,是否應該創建一個單獨的Connection對像?
  要想正確回答這個問題,需要在兩個不同情況下檢驗測試結果:第一是每頁執行一個數據庫處理的情況,第二是每頁執行多個數據庫處理的情況。

  在前面的例子中,我們已經創建了一個單獨的Connection對象,並將它傳遞到記錄集的ActiveConnection 屬性。但是也有可能僅僅把連接字符串傳遞到這個屬性中,從而可以避免一個額外的步驟,即在腳本( ADO__03.asp )中例示和配置一個單獨的組件:

  objRS.ActiveConnection = Application("Conn")


  儘管我們仍然在記錄集中創建了一個連接,但它是在非常優化的情況下創建的,所以剛一開始我們就看到啟動時間比以前的測試減少了23%,同預料中一樣,同每個記錄的顯示時間幾乎沒有什麼差別。

  因此,我們的第二個規則是:

  * 當使用一個單個記錄集時,將連接字符串傳遞到ActiveConnection屬性中。

  下面要確定當在一個頁面上創建多個記錄集時,這個邏輯是否依然成立。為測試這個情況,我引入了FOR 循環,將前面的例子重複10次。在這個測試中,我們還將研究3種選擇:

  第一,我們在每個循環中創建並銷毀Connection 對像( 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

  第二,在循環外創建一個單獨的Connection 對象,並與每個記錄集共享它( 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

  第三,在每個循環中將連接字符串傳遞到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


  你可能已經猜到了,在每個循環中創建並銷毀Connection 對象是一個低效率的方法。但是令人吃驚的是,僅僅在每個循環中傳遞連接字符串比共享單一連接對象的效率只低一點點。

  儘管如此,我們的第3條規則是:

  * 在一個頁面上使用多個記錄集時,創建一個Connection 對象,在ActiveConnection 屬性中重複使用它。

指針和鎖的類型中,哪些是最有效的?
  到目前為止,我們所有測試都只用了只向前(Forward Only )的指針在記錄集中循環。但是,ADO還為記錄集提供了3種類型的指針:Static(靜態), Dynamic(動態)和 Keyset(鍵盤)。每一種都提供了額外的功能,比如向前和向後移動以及當別人建立數據時可以看到修改情況的功能。不過,討論這些指針類型的內涵不是本文討論的範圍。我把這些留給你自己。下面是各種類型的比較分析。


  與它們的同類Forward Only 相比,這些額外的指針都明顯地造成了更大的負載( ADO__03.asp )。另外這些指針在循環期間也更慢。我想與你一起分享的一條忠告是要避免這種想法:「我不時地需要一下Dynamic 指針,所以乾脆總是用它算了。」

  從本質上說,同樣的問題也適用於鎖的類型。前面的測試中只使用了Read Only(只讀)類型的鎖。但是,還有三種類型的鎖:Lock Pessimistic、 Lock Optimistic和Lock Batch Optimistic。同指針的選擇一樣,這些鎖也為處理記錄集中的數據提供了額外的功能和控制。同樣,我將學習每種鎖設置的適當用途的內容留給你自己。


  所以引導我們考慮規則4的邏輯很簡單:使用最適合你的任務的最簡單的指針和鎖的類型。

獲取一個記錄集最好的方式是什麼?
  到目前為止,我們只是通過Recordset 對像來恢復記錄集。但是ADO還提供了一些獲取記錄集的間接方法。下一個測試就將ADO__03.asp 中的值與直接從一個Connection對像中創建一個記錄集對像( CONN_01.asp )來比較。

  Set objConn = Server.CreateObject("ADODB.Connection")

  objConn.Open Application("Conn")

  Set objRS = objConn.Execute(Application("SQL"))


  我們看到,負載有一個輕微的增加,顯示每條記錄的時間沒有變化。

  然後,我們看看從一個Command 對像中直接創建一個Recordset 對像( CMD__01.asp ):

  Set objCmd = Server.CreateObject("ADODB.Command")

  objCmd.ActiveConnection = Application("Conn")

  objCmd.CommandText = Application("SQL")

  Set objRS = objCmd.Execute


  我們再次看到負載有一個輕微的增加,每個記錄的顯示時間有一個名義上的區別。雖然最後這兩種方法對性能的影響很小,卻有一個大問題需要考慮。

  通過Recordset 類創建一個記錄集對於控制如何處理記錄集提供了最大的靈活性。雖然其它方法也沒有提出一個壓倒性的性能問題,但是你會被默認狀態下返回何種指針類型和鎖類型而困惑,這些對於你的特定需求來說不一定是最優的。

  所以,除非因為某種特殊原因你需要其它方法的話,請遵循第5條規則:通過ADODB.Recordset 類例示記錄集以獲得最好的性能和最大的靈活性。

是否應該斷開記錄集?
  ADO為斷開一個記錄集提供了一種選擇,記錄集要在一個向前查詢中恢復所有數據、關閉連接、使用一個本地(或客戶)指針在數據集中移動。這還提供了一個早期釋放連接的機會。這種情況對於處理遠程數據服務是必要的,因為這種情況下數據必須從數據庫斷開。但是對於普通的用途,這樣做有好處嗎?

  下面我們增加了CursorLocation 屬性,打開記錄集後關閉連接( 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


  從理論上說,這個技術應該導致性能更快。原因有兩個:首先,在記錄集中移動時,避免了通過連接的重複請求;其次通過較早地取消連接減輕了資源需求。但是,在使用客戶端指針時,效率低得很明顯。可能是由於當使用客戶指針位置時,不管你的設置是什麼,CursorType 都被修改成Static。

  規則6是這樣的:除非是一個斷開的環境中所要求的,避免使用斷開的記錄集。

什麼是設置記錄集屬性的最好方法?
  前面所有的測試都是通過單獨的屬性設置來直接設置記錄集的屬性的。但是Recordset.Open 函數可以為我們所需要的全部屬性接收額外的參數。雖然對於每個屬性來說,單獨的代碼行易於閱讀和維護,它們還是要分別執行一個單獨函數調用,必須通過COM界面來集合( ADO__07.asp ):

  Set objRS = Server.CreateObject("ADODB.Recordset")

  objRS.Open Application("SQL"), Application("Conn"), 0, 1

  ' adForwardOnly, adLockReadOnly


  這些方法在負載上帶來得差別小得驚人,於是我們得到規則7:不要對單獨設置記錄集屬性感到擔心


引用記錄集中域值的最有效方法是什麼?
  到目前為止,我都是用名字引用記錄集中的域值的。這可能是一種效率很低的方法,因為每次調用都需要查找域。為了證明這一點,下面的測試就要通過記錄集中域的集合的指針來引用域(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


  正如我們所預料的,裝載時間的變化很小(差異可能是由於代碼上的輕微減少引起的)。但是這種技術在有效顯示時間上卻帶來了明顯的減少。

  在下面的例子中,我們將給每個域指定一個單獨的變量。這種方法避免了在表格循環內的所有查找( 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


  到目前,這種方法形成的結果是最好的。每條記錄的顯示時間下降成了.45 毫秒。

  現在,所有測試腳本的配置都要求對結果記錄集有一些瞭解。比如說,我們一直在欄標題中給域名編碼,單獨地引用這些域的值。下面的例子提供了一個動態的解決方案,在域的集合中循環,不僅得到數據,也得到域的標題(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


  可以看到,我們在性能上有一個損失,但是這個方法還是比ADO__07.asp要快一些。

  下面的測試是在最後兩個測試之間進行一些折中。通過在一個動態分配數組中保存域的引用,既維持了動態的靈活性,也挽回了一些性能上的損失。

  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


  雖然它並不比最好值快,但是比前面的幾個例子要快了很多,並且有一個優勢就是能夠動態地表現任何記錄集。

  在下一個測試中,我們將對以前的方案做一個徹底的改變,使用記錄集的GetRows指令創建一個循環用的數組,而不是在記錄集本身進行循環。注意,調用GetRows之後,立刻就將記錄集設置為Nothing,這樣就能更快地釋放系統資源。另外還要注意數組的第一個維數代表域,第二個維數代表行 ( 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


  通過使用GetRows 指令,就可以獲取整個記錄集並將其裝載到數組中。當恢復特別大的記錄集時,這種方法有可能會造成資源問題,但是數據的循環快多了,因為類似於MoveNext 的函數調用和EOF 的檢測都可以取消了。

  不過速度的提升確實是有代價的,因為記錄集的元數據不再與數據在一起。圍繞這個問題,我在調用GetRows之前用記錄集來恢復標題名。另外還可以提前提取數據類型和其它信息。還要注意,在我們的測試中,性能上的優勢只有在使用大一些的記錄集時才能看到。

  在這部分最後的測試中,我們更進一步,使用記錄集的GetString 指令。這個方法將整個記錄集提取到一個大的字符串中,允許你指定自己的分隔符( 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


  雖然這種方法已經接近了最高水平,但是它只適合於最簡單的設計,因為它根本就不能應用於數據的特殊情況。

觀察
  在我們開始這套測試之前,執行每條記錄的時間一直在.83 毫秒左右震動。這套測試中的大多數方法都將這個數字減少了一半。雖然有些方法明顯地提供了更快的速度,但是代價是靈活性的降低。

  下面的規則是以重要程度為順序的:

* 當記錄集中的值不需要用一種特殊方式來對待並且能夠格式化為一種統一的格式時,使用GetString方法來提取數據。
* 當你在設計上需要更大的靈活性,但是又不需要用記錄集的元數據進行工作,使用GetRows 方法將數據提取到一個數組中。
* 當你需要設計的靈活性和元數據時,在進入一個數據恢復的循環之前,將你的域約束在本地變量中。避免用名字引用域。
使用臨時字符串可以較好地代替緩衝器嗎?
  這是針對我上一篇文章提交的一些註解所引發的一個小小的離題。要討論的問題是圍繞著緩衝器的使用及使用臨時字符串作為替代來收集輸出,這樣就允許Response.Write 只調用一次。為了測試,我從ADO_11.asp的代碼開始,將結果附加到一個字符串中,而不是在每個循環都調用Response.Write,當整個操作都結束後,在字符串上調用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)


  看起來執行得不是很好。也許正像許多人建議的,我們應該用Space 指令為這個字符串指定一些空間,這樣它就不需要在循環期間總是為自己重新分配空間( STR__02.asp ):

  Dim strTable

  strTable = Space(10000)


  也許Space 指令並不像建議的那樣工作。我們最後的規則是:不要用臨時字符串來收集輸出。

規則的總結
  現在我們來重新總結一下這些規則:

  * 避免包含ADOVBS.inc文件,用其它方法來使用常量。

  * 當使用一個單個記錄集時,將連接字符串傳遞到ActiveConnection屬性中。

  * 在一個頁面上使用多個記錄集時,創建一個Connection 對象,在ActiveConnection 屬性中重複使用它。

  * 使用最適合你的任務的最簡單的指針和鎖的類型。

  * 通過ADODB.Recordset 類例示記錄集以獲得最好的性能和最大的靈活性。

  * 除非是一個斷開的環境中所要求的,避免使用斷開的記錄集。

  * 不要對單獨設置記錄集屬性感到擔心。

  * 當記錄集中的值不需要用一種特殊方式來對待並且能夠格式化為一種統一的格式時,使用GetString方法來提取數據。

  * 當你在設計上需要更大的靈活性,但是又不需要用記錄集的元數據進行工作,使用GetRows方法將數據提取到一個數組中。

  * 當你需要設計的靈活性和元數據時,在進入一個數據恢復的循環之前,將你的域約束在本地變量中。避免用名字引用域。

  * 不要用臨時字符串來收集輸出。

結論
  同樣,從這些測試中我們所學到的最重要的一點是:小小的變化會在性能上造成很大的影響。如果我們把第一個測試與ADO__09.asp(在記錄集中循環的最快結果)相比,可以看到在反應時?br>

  所以要記住,永遠不要想當然。如果你不能肯定,那就運行一些有針對性的測試。
相關文章: