ASP.NET性能优化探讨与分享
数据库优化
要提高数据库的运行效率,需要从数据库系统级、数据库设计级、程序实现级这三方面优化。
1、数据库系统级
主要是数据库的运行服务器硬件条件。选取高性能的服务器。
2、数据库设计级
(1) 在数据库物理设计时,合理设计主键与外键,适当增加数据冗余, 少用触发器, 多用存储过程。
(2) 计算非常复杂、而且记录条数非常巨大时(千万条级别),要先在数据库外面以文件系统方式计算处理完成之后入库追加到数据表。
(3)数据表记录太多,超过一千万条,则要对该表进行水平分割。水平分割的做法是,以该表主键PK的某个值为界线,将该表的记录水平分割为两个表。若发现某个表的字段太多,例如超过七十个,则垂直分割该表,将原来的一个表分解为两个表。
(4) 对数据库管理系统DBMS进行系统优化,即优化各种系统参数,如缓冲区个数。
(5) 在使用面向数据的SQL语言进行程序设计时,尽量采取优化算法。
3、程序实现级
(1)合理掌控数据库的连接和关闭,数据操作完毕关闭数据库连接。
访问数据库资源需要创建连接、打开连接和关闭连接几个操作。这些过程需要多次与数据库交换信息以通过身份验证,比较耗费服务器资源。Asp.Net中提供了连接池(Connection Pool)改善打开和关闭数据库对性能的影响。系统将用户的数据库连接放在连接池中,需要时取出,关闭时收回连接,等待下一次的连接请求。
连接池的大小是有限的,如果在连接池达到最大限度后仍要求创建连接,必然大大影响性能。因此,在建立数据库连接后只有在真正需要操作时才打开连接,使用完毕后马上关闭,从而尽量减少数据库连接打开的时间,避免出现超出连接限制的情况。
SqlConnection conn = new SqlConnection();
con.ConnectionString = "server=localhost;database=Northwind.mdf;uid=sa;pwd=sa";
string sql = "select top 5 CustomerID,CompanyName from Customers order by CustomerID";
SqlCommand cmd = new SqlCommand(sql, con);
//其他操作代码;
conn.Open();
//其他操作代码;
SqlDataReader dr = cmd.ExecuteReader();
while (dr.Read())
{
//相关操作代码;
}
conn.close();
(2)尽量使用存储过程,并优化查询语句。
尽量使用存储过程,存储过程是存储在服务器上的一组预编译的SQL语句,类似于DOS系统中的批处理文件。存储过程具有对数据库立即访问的功能,信息处理极为迅速。使用存储过程可以避免对命令的多次编译,在执行一次后其执行规划就驻留在高速缓存中,以后需要时只需直接调用缓存中的二进制代码即可。另外,存储过程在服务器端运行,独立于ASP.NET程序,便于修改,最重要的是它可以减少数据库操作语句在网络中的传输。
优化查询语句,ASP.NET中ADO连接消耗的资源相当大,SQL语句运行的时间越长,占用系统资源的时间也越长。因此,尽量使用优化过的SQL语句以减少执行时间。比如,不在查询语句中包含子查询语句,充分利用索引等。在实际的编码中少用"select * from table",而更根据实际需求有选择筛选字段,减少返回字段和数据量,减轻数据库压力。
(3)合理使用SqlDataReader和DataSet 。对于只读数据访问用SqlDataReader,不要使用DataSet,
SqlDataReader类提供了一种读取从SQL Server 数据库检索的只进数据流的方法。如果当创建 ASP.NET 应用程序时出现允许您使用它的情况,则 SqlDataReader类提供比 DataSet类更高的性能。情况之所以这样,是因为 SqlDataReader使用SQL Server 的本机网络数据传输格式从数据库连接直接读取数据。另外,SqlDataReader类实现 IEnumerable接口,该接口也允许您将数据绑定到服务器控。
DataSet作为一个功能强大的支持离线的数据库,其对性能的开销也相对较大SqlDataReader优点:读取数据非常快。如果对返回的数据不需做大量处理的情况下,建议使用SqlDataReader,其性能要比DataSet好很多,但缺点是:直到数据读完才可Close()数据库的连接。DataSet是把数据读出,缓存在内存中缺点:对内存的占用较高如果对返回的数据需做大量的处理用DataSet比较好些,可以减少对数据库的连接操作。优点:只需连接一次就可Close()数据库的连接。
一般情况下,读取大量数据,对返回数据不做大量处理用SqlDataReader.对返回数据大量处理用DataSet比较合适。对SqlDataReader和DataSet的选择完全取决于程序功能的实现要求。
(4)无论SqlDataReader和DataSet,返回多个结果集,然后用rd.NextResult() 或ds.Tables[i]来分别处理数据,可以减少重复连接数据库的次数,同时尽量用比较高效的SQL代替后续复杂的DataSet二次加工。
程序设计优化
程序设计优化需要在系统的框架、代码可读和效率、页面以及其他非代码元素三方面进行。
1、系统的框架
采用合理优秀的框架设计系统,并不是一味的求新或者是追求逻辑上的复杂程度。
2、代码可读和效率
(1)使用值类型的ToString()方法。在连接字符串时,经常使用"+"符号直接将数字添加到字符串中这种方法虽然简单,也可以得到正确结果,但是由于涉及到不同的数据类型,数字需要通过装箱操作转化为引用类型才可以添加到字符串中。但是装箱操作对性能影响较大,因为在进行这类处理时,将在托管堆中分配一个新的对象,原有的值复制到新创建的对象中。使用值类型的ToString()方法可以避免装箱操作,从而提高应用程序性能。
(2)运用StringBuilder类。String类的对象是不可改变的,对于String对象的重新赋值在本质上是重新创建了一个String对象并将新值赋予该对象。在处理字符串时,最好使用StringBuilder类,其.NET命名空间是System.Text该类并非创建新的对象,而是通过Append,Remove,Insert等方法直接对字符串进行操作,通过ToString()方法返回操作结果,对性能的提高并非很显著。
(3)使用 HttpServerUtility.Transfer() 方法在同一应用程序的页面间重定向,采用 Server.Transfer() 语法,在页面中使用该方法可避免不必要的客户端重定向Response.Redirect()
(4)避免使用ArrayList
因为任何对象添加到ArrayList都要封箱为System.Object类型,从ArrayList取出数据时,要拆箱回实际的类型。建议使用自定义的集合类型代替ArrayList。ASP.NET 2.0提供了一个新的类型,叫泛型,这是一个强类型,使用泛型集合就可以避免了封箱和拆箱的发生,提高了性能。(有关泛型资料请参考http://msdn.microsoft.com/zh-cn/library/d5x73970(VS.80).aspx)
(5)使用Hashtable代替其他字典集合类型(如System.Collections.Specialized.StringDictionary , System.Collections.Specialized.NameValueCollection ,System.Collections.Specialized.StringCollection),存放少量数据的时候可以使用Hashtable.
(6)为字符串容器声明常量,不要直接把字符封装在双引号" "里面。
//避免
MyObject obj = new MyObject();
obj.Status = "ACTIVE";
//推荐
const string C_STATUS = "ACTIVE";
MyObject obj = new MyObject();
obj.Status = C_STATUS;
(7)不要用ToUpper() ToLower()转换字符串进行比较,用string.Compare()代替,它可以忽略大小写进行比较。
const string C_VALUE = "COMPARE";
if (String.Compare(sVariable, C_VALUE, true) == 0)
{
Console.Write("相同");
}
也可以用str == String.Empty或者str.Length == 0判断是否为空,将String对象的Length属性与0比较是最快的方法,避免不必要的调用 ToUpper()或 ToLower()方法。
(8)类型转化Int32.TryParse()优于Int32.Parse()优于Convert.ToInt32()。
//推荐
.NET1.1下用Int32.Parse();.NET2.0用Int32.TryParse()
因为:
Convert.ToInt32 会把最终的解析工作代理给 Int32.Parse
Int32.Parse 会把最终的解析工作代理给 Number.ParseInt32
Int32.TryParse 会把最终的解析工作代理给Number.TryParseInt32
(9)如果只是从XML对象读取数据,用只读的XPathDocument(System.Xml.XPath)代替XmlDocument (System.Xml),可以提高性能。
//避免
XmlDocument xmld = new XmlDocument();
xmld.LoadXml(sXML);
txtName.Text = xmld.SelectSingleNode( "/packet/child").InnerText;
//推荐
XPathDocument xmldContext = new XPathDocument(new StringReader(oContext.Value));
XPathNavigator xnav = xmldContext.CreateNavigator();
XPathNodeIterator xpNodeIter = xnav.Select("packet/child");
iCount = xpNodeIter.Count;
xpNodeIter = xnav.SelectDescendants(XPathNodeType.Element, false);
while (xpNodeIter.MoveNext())
{
sCurrValues += xpNodeIter.Current.Value + ",";
}
(10)避免在循环体里声明变量,应该在循环体外声明变量,在循环体里初始化。C#程序开发要遵循的一个基本原则就是避免不必要的对象创建。
//避免
for (int i = 0; i < 10; i++)
{
SomeClass objSC = new SomeClass();
}
//推荐
SomeClass objSC = null;
for (int i = 0; i < 10; i++)
{
objSC = new SomeClass();
}
(11)捕获指定的异常,不要使用通用的System.Exception
//避免
try
{
<some logic>
}
catch(Exception exc)
{
<Error handling>
}
//推荐
try
{
<some logic>
}
catch(System.NullReferenceException exc)
{
<Error handling>
}
catch(System.ArgumentOutOfRangeException exc)
{
<Error handling>
}
catch(System.InvalidCastException exc)
{
<Error handling>
}
(12)使用try...catch...finally时, 要在finally里释放占用的资源如连接,文件流等。不然在catch到错误后占用的资源不能释放。
try
{ }
catch
{ }
finally
{
connection.close();
}
(13)避免使用递归调用和嵌套循环,使用他们会严重影响性能,在不得不用的时候才使用。
(14)禁用VB和Jscript动态数据类型。应当始终显示地申明变量数据类型,这能够节约程序的执行时间以往,开发人员喜欢使用VBScript和JavaScript的原因之一就是它们所谓无类型的性质变量不需要显式类型声明,并能够简单地通过使用来创建它们当从一个类型到另一个类型进行分配时,转换将自动执行不过,这种便利会大大损害应用程序的性能。如:为了获得最佳的性能,当声明 JavaScript .NET 变量时,请为其分配一个类型例如,var A : String
(15)使用缓存。ASP.NET中常用的缓存方式有:
1)页面缓存(对整个页面进行缓存)
<%@ OutputCache VaryByParam="classid;page" Duration="3600" %>
2)片断缓存(对页面的某一部分,如某个Control进行缓存)
<%@ OutputCache Duration="60" VaryByParam=TextBox1;TextBox2 %>
3)数据缓存
Cache [关键字] = 关键字的取值;然后通过下面的方法来访问这个对象:
string mKeyValue = string.Empty;
if (Cache[关键字] != null)
{
mKeyValue = Cache[关键字];
}
注意Page.Cache和HttpContext.Current.Cache区别:它们指的同一个对象,在Page里,用Page.Cache,如果在global.asax或自己的类里用: HttpContext.Current.Cache,在有些事件中,由于其没有HttpContext,就用HttpRuntime.Cache。
使用Output Cache缓存数据。
缓存数据的规则:第一,数据可能会被频繁的被使用,这种数据可以缓存。第二,数据的访问频率非常高,或者一个数据的访问频率不高,但是它的生存周期很长。第三,应该估计缓存集的大小,把缓存集的大小限制在一定的范围内,避免出现内存溢出的错误。
缓存机制:首先是缓存实现了最近使用原则(a least-recently-used algorithm),当缓存少的时候,它会自动的强制清除那些无用的缓存。其次 条件依赖强制清除原则(expiration dependencies),条件可以是时间,关键字和文件以时间作为条件是最常用的在asp2.0中增加一个更强的条件,就是数据库条件当数据库中的数据发生变化时,就会强制清除缓存。
使用 ASP.NET缓存机制有两点需要注意:首先,不要缓存太多项缓存每个项均有开销,特别是在内存使用方面不要缓存容易重新计算和很少使用的项;其次,给缓存的项分配的有效期不要太短,很快到期的项会导致缓存中不必要的周转,并且经常导致更多的代码清除和垃圾回收工作。
应用总结:
应该:
应该缓存那些经常被访问同时变化频率不大的数据
应该缓存整个应用程序都要使用的设置或对象,但这些设置和对象必须在其生存期内不会变化
不应该:
不要缓存个人信息如果缓存个人信息,其他人很容易取得这些信息
不要缓存包含基于时间值的页面,否则浏览者将无法理解为何时间总是滞后
不要缓存用户随时都会修改的对象,如类似购物车的相关信息
3、页面以及其他非代码元素
(1)不使用不必要的服务器控件(Server Control)。大量的服务器端控件方便了程序开发,但也可能带来性能的损失,因为用户每操作一次服务器端控件,就产生一次与服务器端的往返过程。所以,尽量选择html控件,能在客户端实现的功能就在客户端实现,减少服务器的压力。
(2)不使用不必要的ViewState。设置控件的 EnableViewState属性设置为 false,禁用ViewState 。
还可以使用 @ Page 指令禁用整个页的视图状态 <%@ Page EnableViewState="false" %>
(3)避免到服务器的不必要的往返过程。
protected void Page_Load(object sender, EventArgs e)
{
// 代码
if (!Page.IsPostBack)
{
// 代码
}
}
(4)当不使用会话状态时禁用它,并且程序开发中尽量少用Session。并不是所有的应用程序或页都需要针对于具体用户的会话状态,您应该对任何不需要会话状态的应用程序或页禁用会话状态。若要禁用页的会话状态,设置 @ Page 指令中的 EnableSessionState属性为 false
若要禁用应用程序的会话状态,设置应用程序 Web.config 文件的 sessionState配置节中将 mode属性为off
(5)合理使用DataGrid、GridView控件。
(6)对数据进行分页。
(7)不要禁用 Web 窗体页的缓冲。除非有特殊的原因要关闭缓冲,否则使其保持打开,禁用 Web 窗体页的缓冲会导致大量的性能开销。
(8) 优化页面结构,减少JavaScript和CSS加载数量。
(9) 页面的图片根据需要进行必要的压缩处理。
Web服务器优化
1、服务器优化
尽可能高的系统硬件配置,主要是内存和CPU的选择。
2、部署方案的优化
根据项目的实际情况,可以建立web service ,将商业逻辑或数据访问放置其中,然后部署在另一台服务器。