程式|最佳化
最佳化是每個開發人員應該關心的問題。對於資料庫訪問,最佳化是一個關鍵問題。和其他任務相比,資料的訪問顯得相對慢些。
因為資料訪問的變化是如此之多,以致於幾乎不可能提出一套固定的資料庫操作的最佳化規則。通常碰到這類問題,經常得到這樣的回答:“這取決於……”,因為這類最佳化問題取決於準備做什麼。
9.3.1 常用的ADO技巧
儘管最佳化取決於所執行的任務,但是仍然有一些常用的技巧:
· 僅選擇所需的列。當開啟記錄集時,不要自動地使用表名(即SELECT *),除非需要獲得所有的列。使用單獨的列意味著將減少發送到伺服器或從伺服器取出的資料的數量。即使需要使用全部列,單獨地命名每個列也會獲得最佳的效能,因為伺服器不必再解釋這些列是什麼名字。
· 儘可能使用預存程序。預存程序是預先編譯的程式,含有一個已經準備好的執行計畫,所以比SQL語句執行得更快。
· 使用預存程序更改資料。這總是比在記錄集上使用ADO方法執行速度快。
· 除非必需否則不要建立記錄集。運行操作查詢時,要確定加入了adExecuteNoRecords選項,這樣記錄集就不會建立。當僅僅返回一個或兩個欄位的單行記錄時(比如ID值),也可以在查詢狀態下使用這種方法。在這種情況下,預存程序和輸出參數將會更快。
· 使用適當的游標和鎖定模式。如果所做的全部工作是從屬記錄集中讀取資料,並將其顯示在螢幕上(比如,建立一個表),那麼使用預設的只能前移的、唯讀記錄集。ADO用來維護記錄和鎖定細節的工作越少,執行的效能就越高。
9.3.2 物件變數
當遍曆記錄集時,一個保證能提高效能的方法是使用物件變數指向集合中的成員。例如,考慮下面的遍曆含有Authors表的記錄集的例子。
While Not rsAuthors.EOF
Response.Write rsAuthors("au_fname") & " " & _
rsAuthors("au_lname") & "<BR>"
rsAuthors.MoveNext
Wend
可以用下面的方法加速代碼執行,同時使其更易於理解。
Set FirstName = rsAuthors("au_fname")
Set LastName = rsAuthors("au_lname")
While Not rsAuthors.EOF
Response.Write FirstName & " " & LastName & "<BR>"
rsAuthors.MoveNext
Wend
這裡使用了兩個變數,並指向記錄集的Fidds集合中的特定欄位(記住,Fidds集合是預設的集合)。因為這裡建立了一個對象的引用,所以可以使用物件變數而不是實際的變數,這意味著指令碼引擎的工作減少了,因為在集合中進行索引的次數變少了。
9.3.3 快取大小
快取的大小是指ADO每次從資料存放區中讀取的記錄的數量,預設為1。這意味著當使用基於伺服器的游標時,每當移動到另一條記錄時,必須從資料存放區中提取記錄。舉一個例子,如果增大快取的大小為10,那麼每次讀ADO緩衝區的記錄數將變為10。如果訪問位於快取內的記錄,那麼ADO不需要從資料存放區中取記錄。當訪問位於快取外的記錄時則下一批記錄將讀入到快取中。
通過使用記錄集的CacheSize屬性,可以設定快取的大小。
rsAuthors.CacheSize = 10
可以在記錄集生命期的任何時候改變快取的大小,但新的數量只在提取下一批記錄後才有效。
與許多改進效能的技巧類似,快取沒有通用的最佳大小,因為它隨任務、資料和提供者的不同而改變。但是,從1開始增加快取的大小總是能提高效能。
如果你想看到這一點,可以使用SQL Server profiler並查看使用預設的快取開啟一個記錄集發生的情況,並比較增大快取後發生的情況。增大快取的大小不僅減低了ADO的工作量,同時也降低了SQL Server的工作量。
9.3.4 資料庫設計
不要希望只通過編程來提高對資料的訪問效率,應該同時考慮一下資料庫的設計。這裡並不打算對資料庫設計進行更多的討論,但在使用Web網站資料庫時應考慮以下幾點:
· 即時資料:向使用者顯示資料時,確保資料內容總是最新是十分重要的。以一份產品目錄為例,目錄內容改變的頻率有多快?如果該目錄並非經常改變,那麼不必每次都從資料庫中提取資料。每周一次,或在資料改變時從資料庫產生一個靜態HTML頁面應是一個更好的辦法。
· 索引:如果需要對錶進行大量的查詢,而不執行太多的添加資料操作,那麼可以考慮為表建立索引。
· 不正常化:如果網站有兩個不同的目的(資料維護與資料分析),那麼可以考慮採用一些不正常化的表以便有助於資料的分析。可以提供獨立的、完全不正常化的但能正常更新的分析用表,為了改善效能甚至可以將這些分析表移到另一台機器上。
· 資料庫統計:如果使用的是SQL Server 6.x,如果資料被添加或刪附除,那麼應定期更新統計結果。這些統計結果用於產生一個查詢計劃,會影響查詢的運行。請閱讀SQL Books Online中的UPDATE STATISTIC以便瞭解更詳細的內容。在SQL Server 7.0中這一任務自動完成。 這些都是十分基本的資料庫設計技巧,但若只埋頭於ASP代碼可能不會考慮到這些。
9.3.5 資料快取
首先需要注意的是,資料快取與記錄集快取雖然都用於改善效能,但兩者是無關的。資料快取是臨時的資料存放區區,允許使用快取中的資料,而不是重建新的資料。這隻適用於那些不經常改動但多次被訪問的資料。
在ASP中一個最簡單的快取資料的方法是使用Application和Session範圍的變數。例如,假設有一些需要選擇書類型的網頁。正常情況下,可能會建立一個含有以下函數的包含檔案。
<%
Function BookTypes()
Dim rsBookTypes
Dim strQuote
strQuote = Chr(34)
Set rsBookTypes = Server.CreateObject ("ADODB.Recordset")
' Get the book types
rsBookTypes.Open "usp_BookTypes", strConn
Response.Write "<SELECT NAME=" & strQuote & lstBookType & strQuote & ">"
While Not rsBookTypes.EOF
Response.Write & "<OPTION>" & rsBookTypes("Type") & "</OPTION>"
rsBookTypes.MoveNext
Wend
Response.Write & "</SELECT>"
rsBookTypes.Close
Set rsBookTypes = Nothing
End Function
%>
這僅僅是調用一個預存程序,從而得到書的類型,同時建立一個SELECT列表。上述代碼的缺點在於每次調用該函數都必須訪問資料庫。因此,重新修改這個函數。
<%
Function BookTypes()
Dim rsBookTypes
Dim strQuote
Dim strList
' See if the list is in the cache
strList = Application("BookTypes")
If strList = "" Then
' Not cached, so build up list and cache it
strQuote = Chr(34)
Set rsBookTypes = Server.CreateObject ("ADODB.Recordset")
' Get the book types
rsBookTypes.Open "usp_BookTypes", strConn
strList = "<SELECT NAME=" & strQuote & lstBookType & strQuote & ">"
While Not rsBookTypes.EOF
strList = strList & "<OPTION>" & rsBookTypes("type") & "</OPTION>"
rsBookTypes.MoveNext
Wend
strList = strList & "</SELECT>"
rsBookTypes.Close
Set rsBookTypes = Nothing
' Check the list
Application("BookTypes") = strList
End If
BookTypes = strList
End Function
%>
這段代碼不只是開啟記錄集,它檢查Application變數BookType的值是否為空白。如果不為空白,則使用該變數的內容。如果為空白,則像以前一樣開啟記錄集。顯然,一旦第一個人運行了這一常式,便緩衝了資料,因此這隻對那些不常變化的資料是有用的。
如果想在使用者基礎上快取資料,可以使用Session範圍的變數,但這裡必須注意Session存在有效期間。到期後會話層變數將和會話一起取消,代碼便有可能終止運行。
利用Web Application Stress(WAS)工具,得到了表9-4的分析結果:
表9-4 利用WAS工具得到的分析結果
很明顯效能有所改善。但不要採用上述方法緩衝一切內容。畢竟,這種方法只適用於那些已經格式化後用於顯示的資料。除此之外,還要考慮到如果Web伺服器只為特定的一個人服務,那幾乎不是一個典型的Web伺服器的用法。使用WAS可以在一個伺服器上類比多個使用者,這樣可以更實際地測試應用程式。
通過類比一定數量的使用者,Web Application Stress工具可以對Web頁面進行承受力測試。該工具有一個簡單的圖形介面,使用起來非常容易。可以從http://homer.rte.microsoft.com/獲得更多的資訊,也可以下載該工具。
快取對象
若要緩衝未格式化過的資料該怎麼辦?可以在不同地方以不同的方式使用嗎?當然,也可以用Application或Session變數這樣做。考慮一下書標題的情況。你或許希望在多個頁面中使用這個標題,也許在一個表格中顯示所有的標題,或在一個列表框中顯示供使用者選擇等等。你可能會想到可以緩衝記錄集本身而無需緩衝含有標籤的HTML文本。
可以在Application或Session變數中緩衝對象,但有兩個主要的問題需要注意:
· 存放在Application變數中的對象必須支援自由線程,因此必須是自由線程對象或雙線程對象。這意味著無法在Application變數中緩衝由VB建立的組件。
· 在Session狀態中存放單元線程對象意味著建立該對象的線程是唯一允許訪問它的線程。因此IIS無法較好地完成線程管理,因為任何試圖訪問這個對象的頁面都必須等待原有線程服務於該頁面。這將扼殺擴充應用程式的任何機會。
對於線程問題的討論參見第15章。
預設情況下,ADO作為單元線程對象裝載,這主要是因為部分OLE DB提供者並非是安全執行緒的。在ADO安裝目錄中有一個註冊表檔案,可將ADO轉換成雙執行緒模式,由此使ADO對象可以安全地存放在Application和Session對象中。
你也許會認為所有的問題都解決了,可以通過使用各種類型的對象獲得顯著的速度提升,但這並不一定。許多人已經認識到既然串連到資料庫是一個相對昂貴的操作,那麼緩衝Connection對象可在再次串連時節省大量的時間。的確如此,但緩衝Connection對象意味著該串連永遠不會關閉,因此串連緩衝池的工作效率比較低。串連緩衝池隱含的一個思想實際上是減少伺服器上使用的資源,而緩衝ASP狀態中物件顯然不能減少資源的使用。事實上還增加了對它們的佔用,因為每緩衝一個對象便要佔用伺服器的資源,對於一個繁忙的網站而言,這將極大地降低Web伺服器的效率。
所以不應儲存Connection對象,但對於Recordset對象,特別是中斷連線的記錄集呢?假定ADO已從單元線程變成了雙線程,就沒有什麼理由不這麼做了,只要確切知道自己在做什麼。不要認為這會自動地改善ASP頁的效能。每一個緩衝的記錄集都在記憶體和ASP管理方面佔用伺服器的資源,因此不要緩衝大的記錄集。
另一個技巧是使用記錄集的GetRows方法,將記錄集轉換成一個數組。因為數組並不像Recordset對象那樣受線程問題的影響,因此非常適合用於會話層的變數。然而它同樣也佔用伺服器資源,還必須考慮處理數組的時間。
構建自己的應用程式,緩衝技巧並非是必要的。