数据库访问性能优化
数据库的连接和关闭
访问数据库资源需要创建连接打开连接和关闭连接几个操作这些过程需要多次与数据库交换信息以通过身份验证比较耗费服务器资源 ASPNET中提供了连接池(Connection Pool)改善打开和关闭数据库对性能的影响系统将用户的数据库连接放在连接池中需要时取出关闭时收回连接等待下一次的连接请求连接池的大小是有限的如果在连接池达到最大限度后仍要求创建连接必然大大影响性能因此在建立数据库连接后只有在真正需要操作时才打开连接使用完毕后马上关闭从而尽量减少数据库连接打开的时间避免出现超出连接限制的情况
使用存储过程
存储过程是存储在服务器上的一组预编译的SQL语句类似于DOS系统中的批处理文件存储过程具有对数据库立即访问的功能信息处理极为迅速使用存储过程可以避免对命令的多次编译在执行一次后其执行规划就驻留在高速缓存中以后需要时只需直接调用缓存中的二进制代码即可另外存储过程在服务器端运行独立于ASPNET程序便于修改最重要的是它可以减少数据库操作语句在网络中的传输
优化查询语句
ASPNET中ADO连接消耗的资源相当大SQL语句运行的时间越长占用系统资源的时间也越长因此尽量使用优化过的SQL语句以减少执行时间比如不在查询语句中包含子查询语句充分利用索引等
字符串操作性能优化
使用值类型的ToString方法
在连接字符串时经常使用+号直接将数字添加到字符串中这种方法虽然简单也可以得到正确结果但是由于涉及到不同的数据类型数字需要通过装箱操作转化为引用类型才可以添加到字符串中但是装箱操作对性能影响较大因为在进行这类处理时将在托管堆中分配一个新的对象原有的值复制到新创建的对象中使用值类型的ToString方法可以避免装箱操作从而提高应用程序性能
运用StringBuilder类
String类对象是不可改变的对于String对象的重新赋值在本质上是重新创建了一个String对象并将新值赋予该对象其方法 ToString对性能的提高并非很显着在处理字符串时最好使用StringBuilder类其NET 命名空间是SystemText该类并非创建新的对象而是通过AppendRemoveInsert等方法直接对字符串进行操作通过 ToString方法返回操作结果
其定义及操作语句如下所示
int num;
SystemTextStringBuilder str = new SystemTextStringBuilder(); //创建字符串
strAppend(numToString()); //添加数值num
ResponseWrite(strToString); //显示操作结果
优化 Web 服务器计算机和特定应用程序的配置文件以符合您的特定需要
默认情况下ASPNET 配置被设置成启用最广泛的功能并尽量适应最常见的方案因此应用程序开发人员可以根据应用程序所使用的功能优化和更改其中的某些配置以提高应用程序的性能下面的列表是您应该考虑的一些选项
仅对需要的应用程序启用身份验证
默认情况下身份验证模式为 Windows或集成 NTLM大多数情况下对于需要身份验证的应用程序最好在 nfig 文件中禁用身份验证并在 nfig 文件中启用身份验证根据适当的请求和响应编码设置来配置应用程序ASPNET 默认编码格式为 UTF如果您的应用程序为严格的 ASCII请配置应用程序使用 ASCII 以获得稍许的性能提高
考虑对应用程序禁用 AutoEventWireup
在 nfig 文件中将 AutoEventWireup 属性设置为 false意味着页面不将方法名与事件进行匹配和将两者挂钩(例如 Page_Load)如果页面开发人员要使用这些事件需要在基类中重写这些方法(例如需要为页面加载事件重写 PageOnLoad而不是使用 Page_Load 方法)如果禁用 AutoEventWireup页面将通过将事件连接留给页面作者而不是自动执行它获得稍许的性能提升
从请求处理管线中移除不用的模块
默认情况下服务器计算机的 nfig 文件中节点的所有功能均保留为激活根据应用程序所使用的功能您可以从请求管线中移除不用的模块以获得稍许的性能提升检查每个模块及其功能并按您的需要自定义它例如如果您在应用程序中不使用会话状态和输出缓存则可以从列表中移除它们以便请求在不执行其他有意义的处理时不必执行每个模块的进入和离开代码
一定要禁用调试模式
在部署生产应用程序或进行任何性能测量之前始终记住禁用调试模式如果启用了调试模式应用程序的性能可能受到非常大的影响
对于广泛依赖外部资源的应用程序请考虑在多处理器计算机上启用网络园艺
ASPNET 进程模型帮助启用多处理器计算机上的可缩放性将工作分发给多个进程(每个CPU一个)并且每个进程都将处理器关系设置为其 CPU此技术称为网络园艺如果应用程序使用较慢的数据库服务器或调用具有外部依赖项的 COM 对象(这里只是提及两种可能性)则为您的应用程序启用网络园艺是有益的但是在决定启用网络园艺之前您应该测试应用程序在网络园中的执行情况
只要可能就缓存数据和页输出
ASPNET 提供了一些简单的机制它们会在不需要为每个页请求动态计算页输出或数据时缓存这些页输出或数据另外通过设计要进行缓存的页和数据请求(特别是在站点中预期将有较大通讯量的区域)可以优化这些页的性能与 NET Framework 的任何 Web 窗体功能相比适当地使用缓存可以更好的提高站点的性能有时这种提高是超数量级的使用 ASPNET 缓存机制有两点需要注意首先不要缓存太多项缓存每个项均有开销特别是在内存使用方面不要缓存容易重新计算和很少使用的项其次给缓存的项分配的有效期不要太短很快到期的项会导致缓存中不必要的周转并且经常导致更多的代码清除和垃圾回收工作若关心此问题请监视与 ASPNET Applications 性能对象关联的 Cache Total Turnover Rate 性能计数器高周转率可能说明存在问题特别是当项在到期前被移除时这也称作内存压力
选择适合页面或应用程序的数据查看机制
根据您选择在 Web 窗体页显示数据的方式在便利和性能之间常常存在着重要的权衡例如DataGrid Web 服务器控件可能是一种显示数据的方便快捷的方法但就性能而言它的开销常常是最大的在某些简单的情况下您通过生成适当的 HTML 自己呈现数据可能很有效但是自定义和浏览器定向会很快抵销所获得的额外功效Repeater Web 服务器控件是便利和性能的折衷它高效可自定义且可编程
将 SqlDataReader 类用于快速只进数据游标
SqlDataReader 类提供了一种读取从 SQL Server 数据库检索的只进数据流的方法如果当创建 ASPNET 应用程序时出现允许您使用它的情况则 SqlDataReader 类提供比 DataSet 类更高的性能情况之所以这样是因为 SqlDataReader 使用 SQL Server 的本机网络数据传输格式从数据库连接直接读取数据另外SqlDataReader 类实现 IEnumerable 接口该接口也允许您将数据绑定到服务器控件有关更多信息请参见 SqlDataReader 类有关 ASPNET 如何访问数据的信息请参见通过 ASPNET 访问数据
将 SQL Server 存储过程用于数据访问
在 NET Framework 提供的所有数据访问方法中基于 SQL Server 的数据访问是生成高性能可缩放 Web 应用程序的推荐选择使用托管 SQL Server 提供程序时可通过使用编译的存储过程而不是特殊查询获得额外的性能提高
避免单线程单元 (STA) COM 组件
默认情况下ASPNET 不允许任何 STA COM 组件在页面内运行若要运行它们必须在 aspx 文件内将 ASPCompat=true 属性包含在 @ Page 指令中这样就将执行用的线程池切换到 STA 线程池而且使 HttpContext 和其他内置对象可用于 COM 对象前者也是一种性能优化因为它避免了将多线程单元 (MTA) 封送到 STA 线程的任何调用使用 STA COM 组件可能大大损害性能应尽量避免若必须使用 STA COM 组件如在任何 interop 方案中则应在执行期间进行大量调用并在每次调用期间发送尽可能多的信息另外小心不要在构造页面期间创建任何 STA COM 组件例如下面的代码中在页面构造时将实例化由某个线程创建的 MySTAComponent而该线程并不是将运行页面的 STA 线程这可能对性能有不利影响因为要构造页面就必须完成 MTA 和 STA 线程之间的封送处理
<%@ Page Language=VB ASPCompat=true %>
<script runat=server>
Dim myComp as new MySTAComponent()
Public Sub Page_Load()
myCompName = Bob
End Sub
</script>
<html>
<%
ResponseWrite(myCompSayHello)
%>
</html>
首选机制是推迟对象的创建直到以后在 STA 线程下执行上述代码如下面的例子所示
<%@ Page Language=VB ASPCompat=true %>
<script runat=server>
Dim myComp
Public Sub Page_Load()
myComp = new MySTAComponent()
myCompName = Bob
End Sub
</script>
<html>
<%
ResponseWrite(myCompSayHello)
%>
</html>
推荐的做法是在需要时或者在 Page_Load 方法中构造任何 COM 组件和外部资源永远不要将任何 STA COM 组件存储在可以由构造它的线程以外的其他线程访问的共享资源里这类资源包括像缓存和会话状态这样的资源即使 STA 线程调用 STA COM 组件也只有构造此 STA COM 组件的线程能够实际为该调用服务而这要求封送处理对创建者线程的调用此封送处理可能产生重大的性能损失和可伸缩性问题在这种情况下请研究一下使 COM 组件成为 MTA COM 组件的可能性或者更好的办法是迁移代码以使对象成为托管对象
将调用密集型的 COM 组件迁移到托管代码
NET Framework 提供了一个简单的方法与传统的 COM 组件进行交互其优点是可以在保留现有投资的同时利用新的平台但是在某些情况下保留旧组件的性能开销使得将组件迁移到托管代码是值得的每一情况都是不一样的决定是否需要迁移组件的最好方法是对 Web 站点运行性能测量建议您研究一下如何将需要大量调用以进行交互的任何COM 组件迁移到托管代码许多情况下不可能将旧式组件迁移到托管代码特别是在最初迁移 Web 应用程序时在这种情况下最大的性能障碍之一是将数据从非托管环境封送到托管环境因此在交互操作中请在任何一端执行尽可能多的任务然后进行一个大调用而不是一系列小调用例如公共语言运行库中的所有字符串都是 Unicode 的所以应在调用托管代码之前将组件中的所有字符串转换成 Unicode 格式另外一处理完任何 COM 对象或本机资源就释放它们这样其他请求就能够使用它们并且最大限度地减少了因稍后请求垃圾回收器释放它们所引起的性能问题
在 Visual Basic NET 或 JScript 代码中使用早期绑定
以往开发人员喜欢使用 Visual BasicVBScript 和 JScript 的原因之一就是它们所谓无类型的性质变量不需要显式类型声明并能够简单地通过使用来创建它们当从一个类型到另一个类型进行分配时转换将自动执行不过这种便利会大大损害应用程序的性能Visual Basic 现在通过使用 Option Strict 编译器指令来支持类型安全编程为了向后兼容默认情况下ASPNET 不启用该选项但是为了得到最佳性能强烈建议在页中启用该选项若要启用 Option Strict请将 Strict 属性包括在 @ Page 指令中或者对于用户控件请将该属性包括在 @ Control 指令中下面的示例演示了如何设置该属性并进行了四个变量调用以显示使用该属性是如何导致编译器错误的
<%@ Page Language=VB Strict=true %>
<%
Dim B
Dim C As String
This will cause a compiler error
A = Hello
This will cause a compiler error
B = World
This will not cause a compiler error
C = !!!!!!
But this will cause a compiler error
C =
%>Dim B
Dim C As String
This will cause a compiler error
A = Hello
This will cause a compiler error
B = World
This will not cause a compiler error
C = !!!!!!
But this will cause a compiler error
C =
%>
JScriptNET 也支持无类型编程但它不提供强制早期绑定的编译器指令若发生下面任何一种情况则变量是晚期绑定的被显式声明为 Object是无类型声明的类的字段是无显式类型声明的专用函数或方法成员并且无法从其使用推断出类型 最后一个差别比较复杂因为如果 JScript NET 编译器可以根据变量的使用情况推断出类型它就会进行优化在下面的示例中变量 A 是早期绑定的但变量 B 是晚期绑定的
var A;
var B;
A = Hello;
B = World;
B = ;
为了获得最佳的性能当声明 JScript NET 变量时请为其分配一个类型例如var A : String
使请求管线内的所有模块尽可能高效
请求管线内的所有模块在每次请求中都有机会被运行因此当请求进入和离开模块时快速地触发代码至关重要特别是在不使用模块功能的代码路径里分别在使用及不使用模块和配置文件时执行吞吐量测试对确定这些方法的执行速度非常有用
使用 HttpServerUtilityTransfer 方法在同一应用程序的页面间重定向
采用 ServerTransfer 语法在页面中使用该方法可避免不必要的客户端重定向
必要时调整应用程序每个辅助进程的线程数
ASPNET 的请求结构试图在执行请求的线程数和可用资源之间达到一种平衡已知一个使用足够 CPU 功率的应用程序该结构将根据可用于请求的 CPU 功率来决定允许同时执行的请求数这项技术称作线程门控但是在某些条件下线程门控算法不是很有效通过使用与 ASPNET Applications 性能对象关联的 Pipeline Instance Count 性能计数器可以在 PerfMon 中监视线程门控当页面调用外部资源如数据库访问或 XML Web services 请求时页面请求通常停止并释放 CPU如果某个请求正在等待被处理并且线程池中有一个线程是自由的那么这个正在等待的请求将开始被处理遗憾的是有时这可能导致 Web 服务器上存在大量同时处理的请求和许多正在等待的线程而它们对服务器性能有不利影响通常如果门控因子是外部资源的响应时间则让过多请求等待资源对 Web 服务器的吞吐量并无帮助为缓和这种情况可以通过更改 nfig 配置文件节点的 maxWorkerThreads 和 maxIOThreads 属性手动设置进程中的线程数限制
注意辅助线程是用来处理 ASPNET 请求的而 IO 线程则是用于为来自文件数据库或 XML Web services 的数据提供服务的分配给这些属性的值是进程中每个 CPU 每类线程的最大数目对于双处理器计算机最大数是设置值的两倍对于四处理器计算机最大值是设置值的四倍无论如何对于有四个或八个 CPU 的计算机最好更改默认值对于有一个或两个处理器的计算机默认值就可以但对于有更多处理器的计算机的性能进程中有一百或两百个线程则弊大于利注意进程中有太多线程往往会降低服务器的速度因为额外的上下文交换导致操作系统将 CPU 周期花在维护线程而不是处理请求上
适当地使用公共语言运行库的垃圾回收器和自动内存管理
小心不要给每个请求分配过多内存因为这样垃圾回收器将必须更频繁地进行更多的工作另外不要让不必要的指针指向对象因为它们将使对象保持活动状态并且应尽量避免含 Finalize 方法的对象因为它们在后面会导致更多的工作特别是在 Finalize 调用中永远不要释放资源因为资源在被垃圾回收器回收之前可能一直消耗着内存最后这个问题经常会对 Web 服务器环境的性能造成毁灭性的打击因为在等待 Finalize 运行时很容易耗尽某个特定的资源
如果有大型 Web 应用程序可考虑执行预批编译
每当发生对目录的第一次请求时都会执行批编译如果目录中的页面没有被分析并编译此功能会成批分析并编译目录中的所有页面以便更好地利用磁盘和内存如果这需要很长时间则将快速分析并编译单个页面以便请求能被处理此功能带给 ASPNET 性能上的好处因为它将许多页面编译为单个程序集从已加载的程序集访问一页比每页加载新的程序集要快批编译的缺点在于如果服务器接收到许多对尚未编译的页面的请求那么当 Web 服务器分析并编译它们时性能可能较差为解决这个问题可以执行预批编译为此只需在应用程序激活之前向它请求一个页面无论哪页均可然后当用户首次访问您的站点时页面及其程序集将已被编译没有简单的机制可以知道批编译何时发生需一直等到 CPU 空闲或者没有更多的编译器进程(例如 cscexe(C# 编译器)或 vbcexe(Visual Basic 编译器))启动还应尽量避免更改应用程序的 bin 目录中的程序集更改页面会导致重新分析和编译该页而替换 bin 目录中的程序集则会导致完全重新批编译该目录在包含许多页面的大规模站点上更好的办法可能是根据计划替换页面或程序集的频繁程度来设计不同的目录结构不常更改的页面可以存储在同一目录中并在特定的时间进行预批编译经常更改的页面应在它们自己的目录中(每个目录最多几百页)以便快速编译Web 应用程序可以包含许多子目录批编译发生在目录级而不是应用程序级
不要依赖代码中的异常
因为异常大大地降低性能所以您不应该将它们用作控制正常程序流程的方式如果有可能检测到代码中可能导致异常的状态请执行这种操作不要在处理该状态之前捕获异常本身常见的方案包括检查 null分配给将分析为数字值的 String 一个值或在应用数学运算前检查特定值下面的示例演示可能导致异常的代码以及测试是否存在某种状态的代码两者产生相同的结果
try
{
result = / num;
}
catch (Exception e)
{
result = ;
}
// to this
if (num != )
result = / num;
else
result = ;
使用 HttpResponseWrite 方法进行字符串串联
该方法提供非常有效的缓沖和连接服务但是如果您正在执行广泛的连接请使用多个 ResponseWrite 调用下面示例中显示的技术比用对 ResponseWrite 方法的单个调用连接字符串更快
ResponseWrite(a);
ResponseWrite(myString);
ResponseWrite(b);
ResponseWrite(myObjToString());
ResponseWrite(c);
ResponseWrite(myString);
ResponseWrite(d);
除非有特殊的原因要关闭缓沖否则使其保持打开
禁用 Web 窗体页的缓沖会导致大量的性能开销
只在必要时保存服务器控件视图状态
自动视图状态管理是服务器控件的功能该功能使服务器控件可以在往返过程上重新填充它们的属性值(您不需要编写任何代码)但是因为服务器控件的视图状态在隐藏的窗体字段中往返于服务器所以该功能确实会对性能产生影响您应该知道在哪些情况下视图状态会有所帮助在哪些情况下它影响页的性能例如如果您将服务器控件绑定到每个往返过程上的数据则将用从数据绑定操作获得的新值替换保存的视图状态在这种情况下禁用视图状态可以节省处理时间默认情况下为所有服务器控件启用视图状态若要禁用视图状态请将控件的EnableViewState 属性设置为 false如下面的 DataGrid 服务器控件示例所示
<asp:datagrid EnableViewState=false datasource= runat=server/>
您还可以使用 @ Page 指令禁用整个页的视图状态当您不从页回发到服务器时这将十分有用
<%@ Page EnableViewState=false %>
注意:@ Control 指令中也支持 EnableViewState 属性该指令允许您控制是否为用户控件启用视图状态若要分析页上服务器控件使用的视图状态的数量请(通过将 trace=true 属性包括在 @ Page 指令中)启用该页的跟蹤并查看 Control Hierarchy 表的 Viewstate 列有关跟蹤和如何启用它的信息请参见 ASPNET 跟蹤
避免到服务器的不必要的往返过程
虽然您很可能希望尽量多地使用 Web 窗体页框架的那些节省时间和代码的功能但在某些情况下却不宜使用 ASPNET 服务器控件和回发事件处理通常只有在检索或存储数据时您才需要启动到服务器的往返过程多数数据操作可在这些往返过程间的客户端上进行例如从 HTML 窗体验证用户输入经常可在数据提交到服务器之前在客户端进行通常如果不需要将信息传递到服务器以将其存储在数据库中那么您不应该编写导致往返过程的代码如果您开发自定义服务器控件请考虑让它们为支持 ECMAScript 的浏览器呈现客户端代码通过以这种方式使用服务器控件您可以显着地减少信息被不必要的发送到 Web 服务器的次数
使用 PageIsPostBack 避免对往返过程执行不必要的处理
如果您编写处理服务器控件回发处理的代码有时可能需要在首次请求页时执行其他代码而不是当用户发送包含在该页中的 HTML 窗体时执行的代码根据该页是否是响应服务器控件事件生成的
使用 PageIsPostBack 属性有条件地执行代码
例如下面的代码演示如何创建数据库连接和命令该命令在首次请求该页时将数据绑定到 DataGrid 服务器控件
void Page_Load(Object sender EventArgs e)
{
// Set up a connection and command here
if (!PageIsPostBack)
{
String query = select * from Authors where FirstName like %JUSTIN%;
myCommandFill(ds Authors);
myDataGridDataBind();
}
}
由于每次请求时都执行 Page_Load 事件上述代码检查 IsPostBack 属性是否设置为 false如果是则执行代码如果该属性设置为 true则不执行代码注意 如果不运行这种检查回发页的行为将不更改Page_Load 事件的代码在执行服务器控件事件之前执行但只有服务器控件事件的结果才可能在输出页上呈现如果不运行该检查仍将为 Page_Load 事件和该页上的任何服务器控件事件执行处理
当不使用会话状态时禁用它
并不是所有的应用程序或页都需要针对于具体用户的会话状态您应该对任何不需要会话状态的应用程序或页禁用会话状态 若要禁用页的会话状态请将 @ Page 指令中的 EnableSessionState 属性设置为 false例如:
<%@ Page EnableSessi %>
注意:如果页需要访问会话变量但不打算创建或修改它们则将@ Page 指令中的 EnableSessionState 属性设置为ReadOnly还可以禁用 XML Web services 方法的会话状态有关更多信息请参见使用 ASPNET 和 XML Web services 客户端创建的 XML Web services若要禁用应用程序的会话状态请在应用程序 nfig 文件的 sessionstate 配置节中将 mode 属性设置为 off例如:
<sessionstate mode=off />
仔细选择会话状态提供程序
ASPNET 为存储应用程序的会话数据提供了三种不同的方法进程内会话状态作为 Windows 服务的进程外会话状态和 SQL Server 数据库中的进程外会话状态每种方法都有自己的优点但进程内会话状态是迄今为止速度最快的解决方案如果只在会话状态中存储少量易失数据则建议您使用进程内提供程序进程外解决方案主要用于跨多个处理器或多个计算机缩放应用程序或者用于服务器或进程重新启动时不能丢失数据的情况有关更多信息请参见 ASPNET 状态管理
不使用不必要的Server Control
中大量的服务器端控件方便了程序开发但也可能带来性能的损失因为用户每操作一次服务器端控件就产生一次与服务器端的往返过程因此非必要应当少使用Server Control
ASPNET应用程序性能测试
在对ASPNET应用程序进行性能测试之前应确保应用程序没有错误而且功能正确具体的性能测试可以采用以下工具进行Web Application Strees Tool (WAS)是Microsoft发布的一个免费测试工具可以从上下载它可以模拟成百上千个用户同时对web应用程序进行访问请求在服务器上形成流量负载从而达到测试的目的可以生成平均TTFB平均TTLB等性能汇总报告 Application Center Test (ACT) 是一个测试工具附带于Visual StudioNET的企业版中是Microsoft正式支持的web应用程序测试工具它能够直观地生成图表结果功能比WAS多但不具备多个客户机同时测试的能力服务器操作系统管理工具中的性能计数器可以对服务器进行监测以了解应用程序性能
结论
对于网站开发人员来说在编写ASPNET应用程序时注意性能问题养成良好的习惯提高应用程序性能至少可以推迟必需的硬件升级降低网站的成本