直播中
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
%>
這僅僅是調(diào)用一個(gè)存儲(chǔ)過程,從而得到書的類型,同時(shí)創(chuàng)建一個(gè)SELECT列表。上述代碼的缺點(diǎn)在于每次調(diào)用該函數(shù)都必須訪問數(shù)據(jù)庫(kù)。因此,重新修改這個(gè)函數(shù)。
<%
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的值是否為空。如果不為空,則使用該變量的內(nèi)容。如果為空,則像以前一樣打開記錄集。顯然,一旦第一個(gè)人運(yùn)行了這一例程,便緩存了數(shù)據(jù),因此這只對(duì)那些不常變化的數(shù)據(jù)是有用的。
如果想在用戶基礎(chǔ)上緩存數(shù)據(jù),可以使用Session范圍的變量,但這里必須注意Session存在有效期。過期后會(huì)話層變量將和會(huì)話一起取消,代碼便有可能終止運(yùn)行。
利用Web Application Stress(WAS)工具,得到了表9-4的分析結(jié)果:
表9-4 利用WAS工具得到的分析結(jié)果
方 法
頁(yè)面點(diǎn)擊次數(shù)
沒有高速緩存
190
有高速緩存
11000
很明顯性能有所改善。但不要采用上述方法緩存一切內(nèi)容。畢竟,這種方法只適用于那些已經(jīng)格式化后用于顯示的數(shù)據(jù)。除此之外,還要考慮到如果Web服務(wù)器只為特定的一個(gè)人服務(wù),那幾乎不是一個(gè)典型的Web服務(wù)器的用法。使用WAS可以在一個(gè)服務(wù)器上模擬多個(gè)用戶,這樣可以更實(shí)際地測(cè)試應(yīng)用程序。
通過模擬一定數(shù)量的用戶,Web Application Stress工具可以對(duì)Web頁(yè)面進(jìn)行承受力測(cè)試。該工具有一個(gè)簡(jiǎn)單的圖形界面,使用起來非常容易??梢詮膆ttp://homer.rte.microsoft.com/獲得更多的信息,也可以下載該工具。
高速緩存對(duì)象
若要緩存未格式化過的數(shù)據(jù)該怎么辦?可以在不同地方以不同的方式使用嗎?當(dāng)然,也可以用Application或Session變量這樣做??紤]一下書標(biāo)題的情況。你或許希望在多個(gè)頁(yè)面中使用這個(gè)標(biāo)題,也許在一個(gè)表格中顯示所有的標(biāo)題,或在一個(gè)列表框中顯示供用戶選擇等等。你可能會(huì)想到可以緩存記錄集本身而無需緩存含有標(biāo)簽的HTML文本。
可以在Application或Session變量中緩存對(duì)象,但有兩個(gè)主要的問題需要注意:
· 存放在Application變量中的對(duì)象必須支持自由線程,因此必須是自由線程對(duì)象或雙線程對(duì)象。這意味著無法在Application變量中緩存由VB創(chuàng)建的組件。
· 在Session狀態(tài)中存放單元線程對(duì)象意味著創(chuàng)建該對(duì)象的線程是唯一允許訪問它的線程。因此IIS無法較好地完成線程管理,因?yàn)槿魏卧噲D訪問這個(gè)對(duì)象的頁(yè)面都必須等待原有線程服務(wù)于該頁(yè)面。這將扼殺擴(kuò)展應(yīng)用程序的任何機(jī)會(huì)。
對(duì)于線程問題的討論參見第15章。
默認(rèn)情況下,ADO作為單元線程對(duì)象裝載,這主要是因?yàn)椴糠諳LE DB提供者并非是線程安全的。在ADO安裝目錄中有一個(gè)注冊(cè)表文件,可將ADO轉(zhuǎn)換成雙線程模型,由此使ADO對(duì)象可以安全地存放在Application和Session對(duì)象中。
你也許會(huì)認(rèn)為所有的問題都解決了,可以通過使用各種類型的對(duì)象獲得顯著的速度提升,但這并不一定。許多人已經(jīng)認(rèn)識(shí)到既然連接到數(shù)據(jù)庫(kù)是一個(gè)相對(duì)昂貴的操作,那么緩存Connection對(duì)象可在再次連接時(shí)節(jié)省大量的時(shí)間。的確如此,但緩存Connection對(duì)象意味著該連接永遠(yuǎn)不會(huì)關(guān)閉,因此連接緩存池的工作效率比較低。連接緩存池隱含的一個(gè)思想實(shí)際上是減少服務(wù)器上使用的資源,而緩存ASP狀態(tài)中的對(duì)象顯然不能減少資源的使用。事實(shí)上還增加了對(duì)它們的占用,因?yàn)槊烤彺嬉粋€(gè)對(duì)象便要占用服務(wù)器的資源,對(duì)于一個(gè)繁忙的站點(diǎn)而言,這將極大地降低Web服務(wù)器的效率。
所以不應(yīng)存儲(chǔ)Connection對(duì)象,但對(duì)于Recordset對(duì)象,特別是斷開連接的記錄集呢?假定ADO已從單元線程變成了雙線程,就沒有什么理由不這么做了,只要確切知道自己在做什么。不要認(rèn)為這會(huì)自動(dòng)地改善ASP頁(yè)的性能。每一個(gè)緩存的記錄集都在內(nèi)存和ASP管理方面占用服務(wù)器的資源,因此不要緩存大的記錄集。
另一個(gè)技巧是使用記錄集的GetRows方法,將記錄集轉(zhuǎn)換成一個(gè)數(shù)組。因?yàn)閿?shù)組并不像Recordset對(duì)象那樣受線程問題的影響,因此非常適合用于會(huì)話層的變量。然而它同樣也占用服務(wù)器資源,還必須考慮處理數(shù)組的時(shí)間。
構(gòu)建自己的應(yīng)用程序,緩存技巧并非是必要的。
9.4 數(shù)據(jù)整形
數(shù)據(jù)整形或分層的記錄集能顯示一個(gè)樹狀結(jié)構(gòu)或相關(guān)記錄集。這通過在記錄集的字段中包含一個(gè)記錄集來實(shí)現(xiàn),可以展現(xiàn)數(shù)據(jù)庫(kù)的關(guān)系,而且多個(gè)記錄集能在一次調(diào)用中返回。有兩個(gè)理由可以解釋它為什么是有用的:
· 性能:當(dāng)正確使用時(shí),數(shù)據(jù)整形可以改善性能。
· 便利:在數(shù)據(jù)整形中非常容易映射父子關(guān)系。
要知道數(shù)據(jù)整形涉及到哪些內(nèi)容,最簡(jiǎn)單的方法是看圖9-8所示的內(nèi)容:
圖9-8 數(shù)據(jù)整形涉及的內(nèi)容
圖9-8顯示了Pubs數(shù)據(jù)庫(kù)中表Publishers、Titles及Sales的層次關(guān)系。
值得注意的一點(diǎn)是每個(gè)子記錄集都不是獨(dú)立的記錄集。因此,圖9-8中只有三個(gè)記錄集,而不是六個(gè)。這是怎么來的呢?在層次關(guān)系中每一層都有一個(gè)記錄集,分別是Publishers、Title和Sales。在Publishers表中引用標(biāo)題時(shí),實(shí)際上是引用了Titles記錄集,但ADO過濾了Titles,所以只顯示那些與被選擇的出版社對(duì)應(yīng)的記錄。這就容易使人誤以為每個(gè)子元素有一個(gè)獨(dú)立的記錄集。
9.4.1 使用數(shù)據(jù)整形
應(yīng)用數(shù)據(jù)整形必須:
· 使用MSDataShape OLEDB提供者。
· 使用一種特殊的整形語(yǔ)言,它是SQL的一種擴(kuò)充,允許構(gòu)造層次。
盡管使用新的提供者,連接字符串的實(shí)際改變不會(huì)太大。這是因?yàn)槿匀恍枰獜哪程帿@取數(shù)據(jù)。因此,可以這么做:
Provider=MSDataShape; Data Provider=SQLOLEDB; Data Source=...
這里用MSDataShape作為提供者,而正常的Provider變?yōu)镈ata Provider,而連接字符串的剩余部分保持不變。
為數(shù)據(jù)整形創(chuàng)建連接字符串的簡(jiǎn)便方法是從創(chuàng)建正規(guī)的連接字符串開始,然后附加到數(shù)據(jù)整形塊的最后。例如,考慮以下正規(guī)的連接字符串。
strConn = "Provider=SQLOLEDB; Data Source=Kanga; " & _
" Initial Catalog=pubs; User Id=sa; Password="
可以像下面這樣為數(shù)據(jù)整形提供者創(chuàng)建連接字符串。
strConn = "Provider=MSDataShape; Data " & strConn
這將提供者設(shè)置為MSDataShape,而Data Provider成為實(shí)際的數(shù)據(jù)源。初始的連接字符串已經(jīng)包含了"Provider= ",所以為了獲得正確的連接細(xì)節(jié),只須前面加上Data。
1. 整形語(yǔ)言
整形語(yǔ)言有其自己的語(yǔ)法,但這里我們不打算涉及其構(gòu)造,它已包含在ADO文獻(xiàn)中。大多數(shù)情況下會(huì)采用以下命令。
SHAPE {parent command} [AS parent alias]
APPEND ({child command} [AS child alias]
RELATE parent_column TO child_column) [AS parent_column_name]
要理解這一點(diǎn),最簡(jiǎn)單的方法是看一個(gè)實(shí)例,以Publishers和Titles為例。
SHAPE {SELECT * FROM publishers}
APPEND ({SELECT * FROM Titles}
RELATE Pub_ID TO PubID) AS rsTitles
第一行是父記錄集,第二行則是子記錄集。第三行指明了關(guān)聯(lián)父、子記錄集的兩個(gè)字段。這個(gè)例子中兩個(gè)表都有一個(gè)名為Pub_ID的字段(出版社ID字段)。這個(gè)命令返回一個(gè)包含出版社的記錄集,在記錄集的最后又附加了一個(gè)含有子記錄集的新列(類型為adChapter)。該列名為AS子句給出,在本例中是rsTitles。
adChapter類型只是說明了該字段含有一個(gè)子記錄集。我個(gè)人認(rèn)為,adChild或adRecordset更合適。
通過遍歷Fields集合,可以很容易看到父記錄集的字段的情況。對(duì)于上面的SHAPE命令,得到圖9-9所示的結(jié)果:
圖9-9 執(zhí)行SHAPE命令后的結(jié)果
訪問子記錄集
現(xiàn)在,我們有了一個(gè)子記錄集,它是另一個(gè)記錄集的一個(gè)字段,那么如何訪問這個(gè)子記錄集呢?很簡(jiǎn)單,可以使用字段的Value屬性來建立另一個(gè)記錄集。
Set rsTitles = rsPublishers("rsTitles").Value
可以遍歷父記錄集,對(duì)應(yīng)于每個(gè)父記錄可以得到一個(gè)子記錄集。下面的代碼能實(shí)現(xiàn)這一點(diǎn)。通常以包含文件、變量聲明開始。
<!-- #INCLUDE FILE="../Include/Connection.asp" -->
<%
Dim rsPublishers
Dim fldF
Dim strShapeConn
Dim strShape
Set rsPublishers = Server.CreateObject ("ADODB.Recordset")
現(xiàn)在創(chuàng)建連接字符串。
' Create the provider command
strShapeConn = "Provider=MSDataShape; Data " & strConn
接下來,輸入實(shí)際的整形命令。這將創(chuàng)建一個(gè)包含出版社的父記錄集和一個(gè)含有書名的子記錄集。
' now the shape command
strShape = "SHAPE {SELECT * FROM Publishers}" & _
" APPEND ({SELECT * FROM Titles}" & _
" RELATE Pub_ID TO Pub_ID) AS rsTitles"
然后正常打開記錄集。
' Open the shaped recordset
rsPublishers.Open strShape, strShapeConn
像正常的記錄集一樣,能夠遍歷記錄。
' loop through the publishers
Response.Write "<UL>"
While Not rsPublishers.EOF
Response.Write "<LI>" & rsPublishers("pub_name")
為了訪問子記錄集,設(shè)置一個(gè)變量來指向那個(gè)包含子記錄集的字段的Value值。本例中該變量為rsTitles。
' now the titles
Response.Write "<UL>"
Set rsTitles = rsPublishers("rsTitles").Value
變量rsTitles在這里是個(gè)記錄集,其作用同普通的記錄集相同。因此,可以遍歷該記錄集的值,該值只包含與父出版者匹配的書名。
' loop through the titles
While Not rsTitles.EOF
Response.Write "<LI>" & rsTitles("title")
rsTitles.MoveNext
Wend
Response.Write "</UL>"
' move to the next publisher
rsPublishers.MoveNext
Wend
Response.Write "</UL>"
rsPublishers.Close
Set rsPublishers = Nothing
Set rsTitles = Nothing
%>
于是得到一份令人滿意的出版社與書名的列表,如圖9-10所示:
圖9-10 整形后的書名列表
用一些DHTML代碼和一些額外的標(biāo)記,就能輕松地隱藏起書名,并且只有選擇出版社時(shí)才顯示相應(yīng)的書名。
2. 多個(gè)子記錄集
如果對(duì)于每個(gè)記錄集僅能有一個(gè)子記錄集,那么數(shù)據(jù)整形就不夠完善。但數(shù)據(jù)整形是極其靈活的。例如可以使用下面的程序來為出版社引用標(biāo)題和雇員。
SHAPE {select * from publishers}
APPEND ({select * from titles}
RELATE pub_id TO pub_id) AS rsTitles,
({select * from employee}
RELATE pub_id To pub_id) AS rsEmployees
只需在APPEND子句后加上其他子記錄集,就能得到圖9-11的結(jié)果:
圖9-11 多個(gè)子記錄集的結(jié)果
訪問子記錄集的方法并沒有改變,仍舊可以用列的Value屬性訪問子記錄集。只是此時(shí)有兩個(gè)子記錄集,因此需要使用兩個(gè)變量。
Set rsTitles = rsPublishers("rsTitles").Value
Set rsEmployees = rsPublishers("rsEmployees").Value
3. 孫代記錄集
在子記錄集自身還包含子記錄集的情況下,可能會(huì)出現(xiàn)孫代記錄集。例如:
SHAPE {SELECT * FROM Publishers}
APPEND (( SHAPE {SELECT * FROM Titles}
APPEND ({SELECT * FROM Sales}
RELATE Title_ID TO Title_ID) AS rsSales)
RELATE Pub_ID TO Pub_ID) AS rsTitles
在第一個(gè)APPEND子句內(nèi)是另一個(gè)SHAPE命令,而不是一個(gè)SELECT語(yǔ)句。與多個(gè)子記錄集的例子相似,訪問孫代記錄集的方法是相同的。
Set rsTitles = rsPublishers("rsTitles").Value
Set rsSales = rsTitles("rsSales").Value
對(duì)子和孫記錄集的深度沒有理論上的限制,但也不可能建立多于三級(jí)或四級(jí)的子記錄集。
9.4.2 性能
數(shù)據(jù)整形不會(huì)自動(dòng)改善性能,但正確使用時(shí)可以改善性能。重要的是記住其工作方式:
· 對(duì)于SHAPE命令中的SELECT語(yǔ)句,將完全取出表中的數(shù)據(jù)。SQL語(yǔ)句并沒有得到任何優(yōu)化。這樣,如果在父表中加入WHERE子句來限制父記錄集的記錄數(shù),仍能得到所有的子記錄集。例如:
SHAPE {SELECT * FROM Publishers WHERE State='CA'}
APPEND ({SELECT * FROM Titles}
RELATE Pub_ID TO Pub_ID) AS rsTitles
APPEND語(yǔ)句返回所有的標(biāo)題,并不僅限于加州(CA)的出版社。記住,這不是SQL JOIN語(yǔ)句。在加州的出版社以及所有的標(biāo)題都被提取了,這樣就完成了數(shù)據(jù)整形。
· 可以使用存儲(chǔ)過程,這會(huì)提高一點(diǎn)性能。然而,如果使用一個(gè)參數(shù)化的存儲(chǔ)過程產(chǎn)生子記錄集,那么每次訪問子記錄集時(shí),這個(gè)存儲(chǔ)過程都會(huì)執(zhí)行。這意味著,子記錄集不在前端代碼中編程產(chǎn)生,而是只包含存在存儲(chǔ)過程產(chǎn)生的那么記錄。不足之處是增加了服務(wù)器的工作,但這樣卻能保證數(shù)據(jù)是最新的,因?yàn)槊看涡枰獣r(shí)就從數(shù)據(jù)庫(kù)中提取數(shù)據(jù)。
在下一章當(dāng)我們著眼于客戶端數(shù)據(jù)時(shí),會(huì)看到更多有關(guān)經(jīng)過整形的記錄集的介紹。
9.5 小結(jié)在下面兩章以及本書的其余部分將看到相關(guān)的其他知識(shí)。在后續(xù)章節(jié)將詳細(xì)探討Web服務(wù)器與瀏覽器之間的交互。畢竟,僅有數(shù)據(jù)是不行的,還要讓人們看見這些數(shù)據(jù)。