|
|
||||
首页
文学作品 网页设计 平面设计 程序设计 考试认证 基础办公 QQ资源 服务器架设 网站运营 网页资源
|
|
|
| 汉南在线 → 网页设计 → Asp动态程序 | |||||||||||
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标记< %= 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代码中增加注释? 在这次的测试中,我们增加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注释对性能的影响很小或根本没有影响。 是否应该为页面明确地设置默认语言? < %@ LANGUAGE=VBSCRIPT % > < % OPTION EXPLICIT Dim FirstName … /app2/language1.asp片段。 基准值= 5.57 msec/page 反应时间= 5.64 msec/page 差= +0.07 msec (增加1.2%) 可以看到,包含了语言的声明对性能有一个轻微的影响。因此: * 设置服务器的默认语言配置以与站点上使用的语言相匹配。 * 除非你使用非默认语言,不要设置语言声明。 如果不需要,是否应该关闭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 会使性能有实质改变吗? 基准值 = 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% 降低) 非常有趣!尽管将变量移到函数范围内增加了额外的函数调用,但实际上却提高了性能。我们又可以增加以下规则: * 在一个页面上,如果代码要使用一次以上,就将代码封入函数区。 * 适当时候,将变量声明移到函数范围内。 使用包含文件有什么影响? 使用内联代码的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 文件。 执行错误处理时会形成多大的负载? < % 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% 增加) 啊!这真实最具有戏剧性的结果。所以请留意以下规则:只有当两个或更多操作被作为一个单元执行时,才使用处理上下文。
< %@ 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% 增加) 听起来可能很明显,但是理解更重要,那就是我们放置在页面上的代码会对性能有影响。页面上的小变化有时会大大地增加反应时间。 规则概括 * 总是将连续Response.Write 语句连接进一个单独语句内。 * 永远不要在Response.Write 周围使用包装函数以附加CRLF。 * 如果必须格式化HTML输出,直接在Response.Write 语句内附加CRLF。 * 总是通过服务器设置开启缓冲器。 * 只要使用适度,ASP注释对性能的影响很小或根本没有影响。 * 设置服务器的默认语言配置以与站点上使用的语言相匹配。 * 除非你使用非默认语言,不要设置语言声明。 * 在VBScript中总是使用Option explicit 。 * 在不需要的情况下,总是在页面或应用程序的水平上关闭Session状态。 * 只有当代码在页面之间共享时才使用Include 文件。 * 在一个页面上,如果代码要使用一次以上,就将代码封入函数区。 * 适当时候,将变量声明移到函数范围内。 * 只有会发生超出测试或控制能力之外的情况时才使用错误句柄。 * 只有当两个或更多操作被作为一个单元执行时,才使用上下文处理。 现在回顾一下,有许多问题可以作为普遍性的方针: * 避免冗余--不要设置那些默认状态下已经设置的属性。 * 限制函数调用的次数。 * 缩小代码的范围。 在本文的第二部分,我们将探索有关ADO和COM对象一些深入的问题。
ADO有很多的功能设置,因此准备这篇文章时最大的挑战便是限制测试问题的范围。考虑到读取大数据集会为web 服务器施加很大的负载,我决定将研究的内容局限在为使用ADO记录集寻找最优化配置的方面。但是这个限制还是提出了一个挑战,因为ADO为执行同一个功能提供了多种方式。比如说,记录集可以从Recordset 类中恢复,也可以从Connection和Command 类中恢复。另外,一旦你有了一个记录集,那么有很多个选择会戏剧性地影响性能。因此,同第一部分一样,我将尽可能地多涉及一些具体问题。 目的 * 是否应该使用ADOVBS.inc包含文件? * 当使用一个记录集时,是否应该创建一个单独的Connection对象? * 恢复一个记录集最好的方法是什么? * 指针和锁的类型中,哪些是最有效的? * 是否应该使用断开的记录集? * 设置记录集(Recordset)属性的最好方法是什么? * 引用记录集中域值的最有效方法是什么? * 使用临时字符串可以较好地代替缓冲器吗? 测试是如何设立的? 为满足这些变化的条件,数据库连接字符串和测试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。要记住的是,同第一篇文章一样,我们只是试图涉及性能方面的问题,而非伸缩性和容量的问题。 还请注意,我们在服务器上开启了缓冲器。另外,我把所有的文件名都定为同样长度,因此文件名中就会有一个或多个下划线来衬垫。 开始 < % 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 包含文件? objRS.CursorType = 0 ' adOpenForwardOnly objRS.LockType = 1 ' adLockReadOnly
解决这个问题有一个很酷的方法,通过将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对象,并将它传递到记录集的ActiveConnection 属性。但是也有可能仅仅把连接字符串传递到这个属性中,从而可以避免一个额外的步骤,即在脚本( ADO__03.asp )中例示和配置一个单独的组件: objRS.ActiveConnection = Application("Conn")
因此,我们的第二个规则是: * 当使用一个单个记录集时,将连接字符串传递到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
尽管如此,我们的第3条规则是: * 在一个页面上使用多个记录集时,创建一个Connection 对象,在ActiveConnection 属性中重复使用它。 指针和锁的类型中,哪些是最有效的?
从本质上说,同样的问题也适用于锁的类型。前面的测试中只使用了Read Only(只读)类型的锁。但是,还有三种类型的锁:Lock Pessimistic、 Lock Optimistic和Lock Batch Optimistic。同指针的选择一样,这些锁也为处理记录集中的数据提供了额外的功能和控制。同样,我将学习每种锁设置的适当用途的内容留给你自己。
获取一个记录集最好的方式是什么? 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 类例示记录集以获得最好的性能和最大的灵活性。 是否应该断开记录集? 下面我们增加了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
规则6是这样的:除非是一个断开的环境中所要求的,避免使用断开的记录集。 什么是设置记录集属性的最好方法? Set objRS = Server.CreateObject("ADODB.Recordset") objRS.Open Application("SQL"), Application("Conn"), 0, 1 ' adForwardOnly, adLockReadOnly
'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
现在,所有测试脚本的配置都要求对结果记录集有一些了解。比如说,我们一直在栏标题中给域名编码,单独地引用这些域的值。下面的例子提供了一个动态的解决方案,在域的集合中循环,不仅得到数据,也得到域的标题(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
下面的测试是在最后两个测试之间进行一些折中。通过在一个动态分配数组中保存域的引用,既维持了动态的灵活性,也挽回了一些性能上的损失。 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之前用记录集来恢复标题名。另外还可以提前提取数据类型和其它信息。还要注意,在我们的测试中,性能上的优势只有在使用大一些的记录集时才能看到。 在这部分最后的测试中,我们更进一步,使用记录集的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
观察 下面的规则是以重要程度为顺序的: * 当记录集中的值不需要用一种特殊方式来对待并且能够格式化为一种统一的格式时,使用GetString方法来提取数据。 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)
Dim strTable strTable = Space(10000)
规则的总结 * 避免包含ADOVBS.inc文件,用其它方法来使用常量。 * 当使用一个单个记录集时,将连接字符串传递到ActiveConnection属性中。 * 在一个页面上使用多个记录集时,创建一个Connection 对象,在ActiveConnection 属性中重复使用它。 * 使用最适合你的任务的最简单的指针和锁的类型。 * 通过ADODB.Recordset 类例示记录集以获得最好的性能和最大的灵活性。 * 除非是一个断开的环境中所要求的,避免使用断开的记录集。 * 不要对单独设置记录集属性感到担心。 * 当记录集中的值不需要用一种特殊方式来对待并且能够格式化为一种统一的格式时,使用GetString方法来提取数据。 * 当你在设计上需要更大的灵活性,但是又不需要用记录集的元数据进行工作,使用GetRows方法将数据提取到一个数组中。 * 当你需要设计的灵活性和元数据时,在进入一个数据恢复的循环之前,将你的域约束在本地变量中。避免用名字引用域。 * 不要用临时字符串来收集输出。 结论 所以要记住,永远不要想当然。如果你不能肯定,那就运行一些有针对性的测试。
| |||||||||||
| >> 相关文章 | |||||||||||
|
授权使用:汉南在线 http://hnzx.hzwz.net/ 经营许可证:陕ICP备05000109号 Powered by:汉南在线 Copyright (c) 2002-2008 汉南在线. All Rights Reserved . |