ASP程序功能测试报告
ASP程序功能测试报告
纲要:ASP动态生成的内容以什么模式输出效率最高?最好用哪种方法提取数据库记载集?本文测试了近20个这类ASP开发中常见的成绩,测试工具所显示的工夫通知咱们:这些通常可能想当然的成绩不只值得关注,而且还有出人意料的秘密隐藏在内。
一、测试目标
本文的第一局部调查了ASP开发中的一些根本成绩,给出了一些功能测试后果以协助读者理束缚入页面的代码到底对功能有什么影响。ADO是由Microsoft开发的一个通用、易用的数据库接口,理想证实经过ADO与数据库交互是ASP最重要的运用之一,在第二局部中,咱们就来钻研这个成绩。
ADO所提供的性能相当宽泛,因此预备本文最大的艰巨在于如何界定成绩的范围。思考到提取大量的数据能够分明地添加Web服务器的负载,所以咱们决议这一局部的次要目标是找出什么才是操作ADO记载集的最优配置。但是,即使增加了成绩的范围,咱们依旧面临很大的艰巨,由于ADO可能有许多种不同的方法来实现同一个义务。例如,记载集不只可能经过Recordset类提取,而且也可能经过Connection和Command类提取;即使失去记载集对象之后,还有许多能够戏剧性地影响功能的操作方法。但是,与第一局部一样,咱们将尽能够地涵盖最宽泛的成绩。
详细地讲,这一局部的指标是搜集足够多的信息,答复下列成绩:
l能否应该经过蕴含引用ADOVBS.inc?
l利用记载集时能否应该创建单独的衔接对象?
l最好用哪种方法提取记载集?
l哪种游标类型和记载锁定模式效率最高?
l能否应该利用本地记载集?
l设置记载集属性用哪种方法最好?
l用哪种方法引用记载集字段值效率最高?
l用暂时字符串搜集输出是一种好方法吗?
二、测试环境
本测试总共用到了21个ASP文件,这些文件可能从本文前面下载。每一个页面设置成可能运转三种不同的查询,分别前往0、25、250个记载。这将协助咱们隔离页面自身的初始化、运转开支与用循环访问记载集的开支。
为便于测试,数据库衔接字符串和SQL命令串都在Global.asa中作为Application变量保存。因为咱们的测试数据库是SQL Server 7.0,因此衔接串指定OLEDB作为衔接提供者,测试数据来自SQL Server的Northwind数据库。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") = "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] "
测试服务器配置如下:450 Mhz Pentium,512 MB RAM,NT Server 4.0 SP5,MDAC 2.1(数据访问组件),以及5.0版本的Microsoft脚本引擎。SQL Server运转在另外一台具备类似配置的机器上。和第一局部一样,咱们依旧利用Microsoft Web Application Stress Tool 记载从第一个页面申请到从服务器接纳到最后一个字节的工夫(TTLB,Time To Last Byte),工夫以毫秒为单位。测试脚本调用每个页面1300次以上,运转工夫约20小时,以下显示的工夫是会话的平均TTLB。请记住,和第一局部一样,咱们只关怀代码的效率,而不是它的可伸缩性或服务器功能。
同时请留意咱们启用了服务器的缓冲。另外,为了让一切的文件名字长度相反,有的文件名字中嵌入了一个或多个下划线。
三、第一次测试
在第一次测试中,咱们模拟Microsoft ASP ADO示例中可找到的典型情景提取一个记载集。在这个例子(ADO__01.asp)中,咱们首先打开一个衔接,然后创建记载集对象。当然,这里的脚本依照本文第一局部所总结的编码规则作了优化。
< % 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"25"栏的TTLB除以25,它是每个记载的总计平均工夫开支。
disp time/25"25"栏的TTLB减去"0"栏的TTLB,然后除以25。该值反映了在循环记载集时显示单个记载所需工夫。
250提取和显示250个记载的TTLB。
tot time/250"250"栏的TTLB除以25,该值代表单个记载的总计平均工夫开支。
disp time/250"250"栏的TTLB减去"0"栏的TTLB,再除以250。该值反映了在循环记载集时显示单个记载所需工夫。
上面的测试后果将用来同下一个测试后果比较。
四、能否应该经过蕴含引用ADOVBS.inc?
Microsoft提供的ADOVBS.inc蕴含了270行代码,这些代码定义了大少数的ADO属性常量。咱们这个示例只从ADOVBS.inc引用了2个常量。因此本次测试(ADO__02.asp)中咱们删除了蕴含文件引用,设置属性时间接利用相应的数值。
objRS.CursorType = 0?' adOpenForwardOnly
objRS.LockType = 1' adLockReadOnly
可能看到页面开支降落了23%。该值并不影响单个记载的提取和显示工夫,由于这里的变化不会影响循环内的记载集操作。有多种方法可能处理ADOVBS.inc的引用成绩。咱们建议将ADOVBS.inc文件作为参考,设置时经过注释加以阐明。请记住,正如第一局部所指出的,过度地使用注释对代码的效率影响极小。另外一种方法是将那些需求用到的常量从ADOVBS.inc文件拷贝到页面内。
还有一个处理该成绩的好方法,这就是经过链接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" -- >
因此,咱们的第一条规则为:
l避免蕴含ADOVBS.inc文件,经过其余方法访问和利用ADO常量。
五、利用记载集时能否应该创建单独的衔接对象?
要正确地答复这个成绩,咱们必须剖析两种不同条件下的测试:第一,页面只要一个数据库事务;第二,页面有多个数据库事务。
在前例中,咱们创建了一个单独的Connection对象并将它赋给Recordset的ActiveConnection属性。但是,如ADO__03.asp所示,咱们也可能间接把衔接串赋给ActiveConnection属性,在脚本中初始化和配置Connection对象这一额外的步骤可能省去。
objRS.ActiveConnection = Application("Conn")
只管Recordset对象依旧要创建一个衔接,但此时的创建是在高度优化的条件下停止的。因此,与上一次测试相比,页面开支又降落了23%,而且如预期的一样,单个记载的显示工夫没有本质的变化。
因此,咱们的第二个规则如下:
l假设只利用一个记载集,间接把衔接串赋给ActiveConnection属性。
接上去咱们反省页面用到多个记载集时,上述规则能否依旧有效。为测试这种情景,咱们引入一个FOR循环将前例反复10次。在这个测试中,咱们将钻研三种变化:
第一,如ADO__04.asp所示,在每一个循环中建设和拆除Connection对象:
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所示,在循环外面创建Connection对象,一切记载集共享该对象:
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所示,在每一个循环内把衔接串赋给ActiveConnection属性:
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
就象咱们可能猜想到的一样,在循环内创建和拆除衔接对象是效率最差的方法。不过,令人惊异的是,在循环内间接把衔接串赋给ActiveConnection属性只比共享单个衔接对象稍微慢了一点。
虽然如此,第三规则应该为:
l同一页面内用到多个记载集时,创建单一的衔接对象并经过ActiveConnection属性共享它。
六、哪种游标类型和记载锁定模式效率最高?
迄今为止的一切测试中咱们只利用了“只能向前”的游标来访问记载集。ADO为记载集提供的游标还有三种类型:静态可滚动的游标,动态可滚动的游标,键集游标。每种游标都提供不同的性能,比如访问前一记载和后一记载、能否可能看到其余程序对数据的修正等。不过,详细探讨每一种游标类型的功用已经超出了本文的范围,下表是各种游标类型的一个比较性的剖析。
和“只能向前”类型的游标相比,一切其它的游标类型都需求额外的开支,而且这些游标在循环内普通也要慢一些。因此,咱们愿与您共享如下告诫:永远不要这样以为——“唔,有时分我会用到动态游标,那么我就不断利用这种游标吧。”
异样的认识也实用于记载锁定模式的抉择。后面的测试只用到了只读的加锁模式,但还存在其余三种模式:激进式、开放式、开放式批解决模式。和游标类型一样,这些锁定模式为解决记载集数据提供了不同的性能和控制才能。
咱们得出如下规则:
l利用适宜于解决义务的最简略的游标类型和记载锁定模式。
七、最好用哪种方法提取记载集?
到目前为止咱们不断经过创建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对象间接创建记载集对象(CMD__02.asp):
Set objCmd = Server.CreateObject("ADODB.Command")
objCmd.ActiveConnection = Application("Conn")
objCmd.CommandText = Application("SQL")
Set objRS = objCmd.Execute
异样,页面开支也略有添加,而单个记载的显示工夫没有实质的变化。前面这两种方法在功能上的差异很小,但咱们还有一个重要的成绩需求思考。
经过Recordset类创建记载集时,咱们可以以最大的灵敏性控制记载集的解决模式。既然前面两种方法未能有压倒性的功能体现,咱们次要还是思考默许前往的游标类型和记载锁定模式,对于某些场合来说默许值并不肯定是最现实的。
因此,除非因为特殊的缘由需求抉择前面两种方法,否则咱们建议思考下面的规则:
l经过ADODB.Recordset类实例化记载集,以获得最好的功能和灵敏性。
八、能否应该利用本地记载集?
ADO容许利用本地(客户端)记载集,此时查询将提取记载集内的一切数据,查询实现后衔接可能立即关闭,当前利用本地的游标访问数据,这为监禁衔接带来了方便。利用本地记载集对于访问那些要求数据离线利用的远程数据服务十分重要,那么,对于一般的运用它能否异样有所协助?
下面咱们退出CursorLocation属性,并在打开记载集之后关闭了衔接(CLIENT1.asp):
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
理论上,这种方法因为以下两个缘由会对效率有所益处:第一,它避免了在记载之间移动时反复地经过衔接申请数据;第二,因为可以方便地监禁衔接,它减轻了资源须要。但是,从上表看起来利用本地记载集对提高效率显然没有什么协助。这或者是由于利用本地记载集时,不管程序设置的是什么,游标总是变成静态类型。
第6个规则如下:
l除非的确要求记载集本地化,否则应避免利用。
十、用哪种方法引用记载集字段值效率最高?
10.1 测试
至此为止咱们不断经过名字引用记载集中的字段值。因为这种方法要求每次都必须寻觅相应的字段,它的效率并不高。为证实这一点,下面这个测试中咱们经过字段在汇合中的索引引用它的值(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
这是目前为止最好的记载。请留意单个记载的显示工夫已经升高到0.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之后立即把Recordset设置成了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方法。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
只管这种方法在速度上的益处十分显著,但它只实用于最简略的操作,基本无奈顺应稍微简单的数据操作要求。