asp.net与mysql性能优化_ASP.NET网站的性能优化

21. 1 程序编码优化

从编码方面提高程序性能的方法主要涉及到集合操作、字符串连接、类型转换等。

21. 1.1 集合操作

在.NET Framework中提供了很多集合类,如ArrayList、BitArray、Hashtable、Queue、SortedList、Stack、ListDictionary、NameValueCollection、OrderedDictionary、StringCollection、List及数组等,要了解各个集合的特性,选择合适的集合。在所有的集合中数组是性能最高的,如果要存储的数据类型一致和容量固定,特别是对值类型的数组进行操作时没有装箱和拆箱操作,效率极高。

在选择集合类型时应考虑几点:

(1)集合中的元素类型是否是一致的,比如集合中将要存储的元素都是int或者都是string类型的就可以考虑使用数组或者泛型集合,这样在存储数值类型元素就可以避免装箱拆箱操作,即使是引用类型的元素也可以避免类型转换操作。

(2)集合中的元素个数是否是固定的,如果集合中存储的元素是固定的并且元素类型是一致的就可以使用数组来存储。

(3)将来对集合的操作集中在那些方面,如果对集合的操作以查找居多可以考虑HashTable或者Dictionary这样的集合,因为在.NET Framework中对这类集合采用了特殊机制,所以在查找时比较的次数比其它集合要少。

另外,在使用可变集合时如果不制定初始容量大小,系统会使用一个默认值来指定可变集合的初始容量大小,如果将来元素个数超过初始容量大小就会先在内部重新构建一个集合,再将原来集合中的元素复制到新集合中,可以在实例化可变集合时指定一个相对较大的初始容量,这样在向可变集合中添加大量元素时就可以避免集合扩充容量带来的性能损失。

下面以一个例子演示一下数组、ArrayList及List集合操作的例子。页面的设计代码如下:

usingSystem;usingSystem.Collections;usingSystem.Collections.Generic;///

///用来测试对集合进行操作所花费的时间///

public classCollectionDemo

{public static voidMain()

{

Test(100000);

Console.WriteLine("==========");

Test(1000000);

Console.WriteLine("==========");

Test(10000000);

Console.ReadLine();

}///

///操作数组///

/// 要操作的次数

///

private static TimeSpan ArrayOperation(intmaxCount)

{//在程序开始运行时记录下系统当前时间DateTime start=DateTime.Now;int[] intList = new int[maxCount];int j = 0;for (int i = 0; i < maxCount; i++)

{

intList[i]=i;

}for (int i = 0; i < maxCount; i++)

{

j=intList[i];

}//在程序结束后记录下系统当前时间DateTime end=DateTime.Now;//用程序结束的系统时间减去开始运行时的时间就是代码运行时间

return end -start;

}///

///

///

///

///

private static TimeSpan ArrayListOperation(intmaxCount)

{//在程序开始运行时记录下系统当前时间DateTime start=DateTime.Now;//用默认的容量来初始化ArrayList//ArrayList intList = new ArrayList();//用指定的容量来初始化ArrayListArrayList intList= newArrayList(maxCount);int j = 0;for (int i = 0; i < maxCount; i++)

{

intList.Add(i);

}for (int i = 0; i < maxCount; i++)

{

j= (int)intList[i];

}//在程序结束后记录下系统当前时间DateTime end=DateTime.Now;return end -start;

}///

///

///

///

///

private static TimeSpan GenericListOperation(intmaxCount)

{//在程序开始运行时记录下系统当前时间DateTime start=DateTime.Now;//用默认的容量来初始化泛型集合//List intList = new List();//用指定的容量来初始化泛型集合List intList = new List(maxCount);int j = 0;for (int i = 0; i < maxCount; i++)

{

intList.Add(i);

}for (int i = 0; i < maxCount; i++)

{

j=intList[i];

}//在程序结束后记录下系统当前时间DateTime end=DateTime.Now;return end -start;

}private static void Test(intmaxCount)

{

TimeSpan ts1=ArrayOperation(maxCount);

TimeSpan ts2=ArrayListOperation(maxCount);

TimeSpan ts3=GenericListOperation(maxCount);

Console.WriteLine("执行" + maxCount + "次操作:");

Console.WriteLine("数组耗时" + ts1.TotalMilliseconds + "毫秒");

Console.WriteLine("ArrayList耗时" + ts2.TotalMilliseconds + "毫秒");

Console.WriteLine("泛型集合耗时" + ts3.TotalMilliseconds + "毫秒");

}

}

对上面的程序代码做几点说明:

(1)上面的代码仅仅是给集合中的元素赋值,然后将集合中的元素取出来,分别用了数组、ArrayList和List泛型集合,并且操作了不同的次数。

(2)在开始运行时获取到系统的当前时间,然后在运行结束之后再次获取系统时间,两次时间之差就是程序运行这段代码所花费的时间,这是一个TimeSpan类型的变量。

(3)为了将测试结果放大,所以操作的次数要尽量设置大一点,实际在网站运行中程序代码也会被成千上万次运行,所以这么做是可以接受的,也使得比较更明显,并且这样也可以减小某些偶然因素带来的干扰。

因为在ASP.NET中测试不稳定因素太多,所以这部分代码是以控制台程序来运行的,运行上面的代码可得到如图21-1所示的效果:

f697b69f50d836cdc237049f165a0dfb.png

图21-1 程序执行结果

在上面的代码中我们是采用了指定ArrayList和List泛型集合的初始化容量大小,可以看出操作在集合元素固定的情况下,数组的操作是最快的,泛型集合的操作次之,ArrayList最慢。

以上测试是针对值类型数据的测试,如果是String这类的引用类型也会有类似的效果,只不过效果引用类型作为集合元素没有值类型作为集合元素明显。

21. 1.2 字符串连接优化

在.NET Framework中String类是一个比较特殊的类,我们知道值类型变量直接在栈中分配内存来存储变量的值,并且不需要垃圾回收器来回收,大部分引用类型变量是在堆中分配内存来存储变量的值,在不再使用的情况下会被垃圾回收器回收所占用的内存。String类型的变量虽然是引用类型变量(常用的赋值方式却很类似于值类型变量的赋值方式,如string a=”123”),但是CLR(Common Language Runtime,通用语言运行时)通过了一种特殊的方法来存放字符串,CLR会维护一个会自动维护一个名为“拘留池”(intern pool,不知道为什么微软会这么叫) 的表,它包含在程序中声明的每个唯一字符串常数的单个实例,以及以编程方式添加的 String 的任何唯一实例。该拘留池节约字符串存储区。如果将字符串常数分配给几个变量,则每个变量设置为引用“拘留池”(intern pool) 中的同一常数,而不是引用具有相同值的 String 的几个不同实例。

看如下代码:

String a=”abc”;

String b=”abc”;

在上面的代码中变量a和变量b都指向了堆中的同一个引用,也就是和下面的代码是等效的:

String a=”abc”;

String b=a;

在给字符串变量赋值时会首先在“拘留池”中检查是否有与要赋值的值相等的字符串,如果存在就会返回该字符串的引用,如果不存在就向字符串“驻留池”中添加该字符串,并且将该字符串的引用返回。这样一来在每次连接字符串时都有可能创建新的字符串对象(如果“驻留池”中不存在对应的字符串的话),从而导致了性能低下。

在String类有个方法专门用来检测“拘留池”中是否存在指定字符串引用的方法,这个方法就是IsInterned(string str)方法,如果存在这个引用则返回str的引用,如果不存在这个引用就返回null。

在需要多次连接字符串时可以考虑使用System.Text.StringBuilder对象,这是一个可变容量的字符串对象。在实例化StringBuilder对象时会指定一个容量(如果不显示指定,则系统默认会指定初始容量为16,如果在程序中最终连接后的容量大于这个值可以自行指定一个较大的值作为初时容量,这样也能提高性能),在进行添加、插入及替换等修改操作时如果不超过容量,则会直接在缓冲区中操作,如果超过容量则会重新分配一个更大的缓冲区,并将原来的数据复制到新缓冲区。

下面通过一个控制台的例子来演示一下String类和StringBuilder类的区别,代码如下:

usingSystem;usingSystem.Text;public classStringDemo

{public static voidMain()

{//如果存在"abc"字符串则a="abc",否则a=null;

string a = string.IsInterned("abc");string b = new StringBuilder().Append("as").Append("df").ToString();if (string.IsNullOrEmpty(a))

{

Console.WriteLine(a+ "不存在'拘留池’中");

}else{

Console.WriteLine(a+ "存在'拘留池’中");

}if (string.IsNullOrEmpty(b))

{

Console.WriteLine(b+ "不存在'拘留池’中");

}else{

Console.WriteLine(b+ "存在'拘留池’中");

}int count = 9000;

TimeSpan ts1=StringConcat(count);

TimeSpan ts2=StringBuilderConcat(count);

Console.WriteLine("使用了String来连接"+count.ToString()+"次耗时"+ts1.TotalMilliseconds+"毫秒");

Console.WriteLine("使用StringBuilder来连接" + count.ToString() + "次耗时" + ts2.TotalMilliseconds + "毫秒");

Console.ReadLine();

}///

///用String对象来连接字符串///

///

///

public static TimeSpan StringConcat(intcount)

{

DateTime start=DateTime.Now;string text = string.Empty;for (int i = 0; i < count; i++)

{

text=text+"0"+i;

}

DateTime end=DateTime.Now;return end -start;

}///

///用StringBuilder对象来连接字符串///

///

///

public static TimeSpan StringBuilderConcat(intcount)

{

DateTime start=DateTime.Now;

StringBuilder text= newStringBuilder();for (int i = 0; i < count; i++)

{

text.Append("0"+i);

}

DateTime end=DateTime.Now;return end -start;

}

}

这个程序的运行效果如图21-2所示:

726594b48091780ad4afdac163f99ff8.png

图21-2 String类和StringBuilder类连接字符串的运行效果

21. 1.3 类型转换优化

在开发中经常会遇到类型转换的问题,一种情况是由字符串类型转换成数值类型,另一种情况是存在继承关系或者实现关系的类之间进行类型转换。在上面的两种转换中如果存在不能转换的情况,则会抛出异常,在引发和处理异常时将消耗大量的系统资源和执行时间。引发异常是为了确实处理异常情况,而不是为了处理可预知的时间或控制流(这一点尤其要注意,不要在代码中来使用异常进行流程控制)。

21. 1.3.1 字符串类型向值类型转换

在.NET Framework2.0版本以前将字符串类型转换成数值类型都是使用Parse()方法,如int.Parse("123")、char.Parse("a")及bool.Parse("TrueString")等等,如果出现了指定的字符串不能转换成相应的数值类型时就会抛出异常,可能会对性能造成不良的影响。在.NET Framework2.0及以后版本中增加了TryParse()方法,减小了性能问题。TryParse()方法使用了两个参数:第一个参数是要转换成数值类型的字符串,第二个参数是一个带有out关键字的参数,并且这个方法有返回值,指示指定的字符串是否能转换成相应的数据类型。如果指定的字符串能转换成相应的数据类型则方法返回true,out参数就是指定字符串转换成相应数值的结果,否则方法返回false,表示不能进行转换而不会抛出异常。

其用法如下面的代码所示:

string str1 = "123";string str2 = "ed1";//因为作为out参数,所以即使是据不变量也不用赋值

intnumber1;//因为作为out参数,所以即使是据不变量也不用赋值

intnumber2;//"123"能转换成int,所以b1=true,number1=123

bool b1 = int.TryParse(str1, outnumber1);//"ed1"不能转换成int,所以b2=false

bool b2 = int.TryParse(str2, out number2);

21. 1.3.2 引用类型之间转换

在引用类型之间转换有两种方式:强制转换和as转换。下面是强制转换的例子:

object d = "asdf";//将d牵制转换成string类型

string e = (string)d;

同字符串类型转换成数值类型一样,如果不存在对应的转换关系也会抛出异常。为了避免引用类型之间转换抛出异常,可以使用as关键字来转换,如下面的代码:

object d ="123";//下面的转换会抛出异常StringBuilder e=(StringBuilder)d;//下面的转换不会抛出异常,并且f为nullStringBuilder f= d as StringBuilder;

在C#中还有一个is关键字,它用来检查对象是否与给定类型兼容,如果兼容表达式的值为true,否则为false。例如下面的表达式:

string a = "asdf";//b1=true,因为string类实现了ICloneable接口

bool b1 = a isICloneable;//b2=true,因为string类是object类的派生类bool b2 = a is object;//b3=false,因为string类与StringBuilder类之间不存在派生或者实现关系

bool b3 = a is StringBuilder;

假如有A类型的变量a和B类型,对于A c=a as B这个转换,存在如下情况:如果A实现或者派生自B,那么上面的转换成功,否则转换不成功,c为null,并且不会抛出异常。

上面讲到的都是关于编码方面提高程序性能应该注意的实现,此外在编码过程中还应注意尽量减少装箱拆箱操作。装箱操作是指将值类型转换成引用类型,拆箱操作是指将引用类型转换成值类型,通过装箱操作使得值类型可以被视作对象。相对于简单的赋值而言,装箱和取消装箱过程需要进行大量的计算。对值类型进行装箱时,必须分配并构造一个全新的对象。次之,取消装箱所需的强制转换也需要进行大量的计算。在向ArrayList这样的非范型集合中添加值类型元素时就会存在装箱过程,再从非范型集合中取出值类型的值就会存在拆箱过程。

21. 1.4 使用Server.Transfer()方法

使用Server.Transfer()方法实现同一应用程序下不同页面间的重定向可以避免不必要的客户端页面重定向。它比Response.Redirect()方法性能要高,并且Server.Transfer()方法具有允许目标页从源页中读取控件值和公共属性值的优点。由于调用了这个方法之后浏览器上不会反应更改后的页的信息,因此它也适合以隐藏URL的形式向用户呈现页面,不过如果用户点击了浏览器上的“后退“按钮或者刷新页面有可能导致意外情况。

21. 1.5 避免不必要的服务器往返

虽然使用服务器控件能够节省时间和代码,但是使用服务器控件有时间会增加页面的往返次数,如果在页面中使用了数据绑定控件,在默认情况下每次响应客户端回发而加载页面时都会重新绑定数据,其实在很多情况下这个过程是没有必要的,使用 Page.IsPostBack 避免对往返过程执行不必要的处理,这个在数据绑定控件那一章有所体现。

21. 1.6 尽早释放对象

在.NET Framework中有很多类实现了IDisposable接口,实现了IDisposable接口的类中都会有一个Dispose()方法,当这些类的实例不再使用时,应及早调用该类的Dispose()方法以释放所占用的资源。

21. 1.7 尽量减少服务器控件的使用

服务器控件在编程中使用起来确实方便,但是这种方便是牺牲了一定的性能为前提的,比如需要在页面某个地方显示一个字符串,这个字符串在任何时候都不会发生变化,那么可以在HTML代码中直接输出,还有有些表单要实现点击按钮之后清空表单输入,利用HTML中的重置按钮就可以完成这个功能,都没有必要使用服务器控件。

21. 2 数据操作优化

数据操作优化方面主要是数据访问优化,主要有数据库连接对象使用、数据访问优化、优化SQL语句、使用缓存等。

21. 2.1 数据库连接对象使用优化

对于数据库连接的使用始终遵循的一条原则是:尽可能晚打开数据库连接,尽可能早关闭数据库连接。这个在ADO.NET一章作过讲述,再次不在赘述。

除此之外,还可以使用数据库连接池来优化。连接到数据库通常需要几个需要很长时间的步骤组成,如建立物理通道(例如套接字或命名管道)、与服务器进行初次握手、分析连接字符串信息、由服务器对连接进行身份验证、运行检查以便在当前事务中登记等等。实际上,大多数应用程序仅使用一个或几个不同的连接配置。这意味着在执行应用程序期间,许多相同的连接将反复地打开和关闭。为了使打开的连接成本最低,ADO.NET 使用称为连接池的优化方法。连接池减少新连接需要打开的次数。池进程保持物理连接的所有权。通过为每个给定的连接配置保留一组活动连接来管理连接。只要用户在连接上调用 Open,池进程就会检查池中是否有可用的连接。如果某个池连接可用,会将该连接返回给调用者,而不是打开新连接。应用程序在该连接上调用 Close 时,池进程会将连接返回到活动连接池集中,而不是真正关闭连接。连接返回到池中之后,即可在下一个 Open 调用中重复使用。

池连接可以大大提高应用程序的性能和可缩放性。默认情况下,ADO.NET 中启用连接池。除非显式禁用,否则,连接在应用程序中打开和关闭时,池进程将对连接进行优化。在开发大型网站时可以更改默认的数据库连接池配置信息,例如可以增加数据库连接池的最大连接数(默认是100),如下面的代码就是将数据库连接池的最大连接数设为200:

Data Source=(local);Initial Catalog=AspNetStudy;User ID=sa;Password=sa;Pooling=true;Min Pool Size=0;Max Pool Size=200

当然也不是设置数据库连接池的最大连接数越大越好,实际上还会受其它因素的限制。

21. 2.2 数据访问优化

如果对数据库中的数据不是需要经常读取,可以使用相应的DataReader对象来读取(如SqlDataReader、OleDbDataReader或OracleDataReader),在这种情况下使用DataReader对象会得到一定的性能提升。

此外,在数据访问时还可以使用存储过程。使用存储过程除了可以防范SQL注入之外,还可以提高程序性能和减少网络流量。存储过程是存储在服务器上的一组预编译的SQL语句,具有对数据库立即访问的功能,信息处理极为迅速。使用存储过程可以避免对命令的多次编译,在执行一次后其执行规划就驻留在高速缓存中,以后需要时只需直接调用缓存中的二进制代码即可。

21. 2.3 优化SQL语句

在开发中除了从C#代码方面优化数据访问之外,还可以从SQL语句上优化数据访问。有人做过调查,在数据量大的库中进行数据访问,不同的人编写的SQL语句所花费的时间有可能相差上百倍,因此尽量让项目中对数据查询优化有经验的人编写SQL语句以提高程序性能。

在优化SQL语句时,有几条原则需要注意:

(1)尽量避免”select * from 表名”这样的SQL语句,特别是在表中字段比较多而只需要显示某几个字段数据的情况下更应该注意这个问题,比如针对SQL Server数据库来说,如果不需要显示或者操作表中的image、Text、ntext及xml这样的字段,就尽量不要出现在select语句中的字段列表中。

(2)尽量不要在查询语句中使用子查询。

(3)尽量使用索引。索引是与表或视图关联的磁盘上结构,可以加快从表或视图中检索行的速度。索引包含由表或视图中的一列或多列生成的键。这些键存储在一个结构中,使数据库可以快速有效地查找与键值关联的行。设计良好的索引可以减少磁盘 I/O 操作,并且消耗的系统资源也较少,从而可以提高查询性能。对于包含 SELECT、UPDATE 或 DELETE 语句的各种查询,索引会很有用。查询优化器使用索引时,搜索索引键列,查找到查询所需行的存储位置,然后从该位置提取匹配行。通常,搜索索引比搜索表要快很多,因为索引与表不同,一般每行包含的列非常少,且行遵循排序顺序。对于常用作where查询的字段可以建立索引以提高查询速度。注意,使用索引后会降低对表的插入、更新和删除速度,在一张表上也不宜建立过多的索引。

21. 2.4 合理使用缓存

在ASP.NET中在不同级别提供了缓存功能,比如控件级和页面级及全局级都提供了缓存功能,在控件中或者页面中都可以通过@ OutputCache指令来使用缓存,这对于减少一些不经常变化并且比较耗时的操作的性能损耗很有用。

除此之外,还有System.Web.Caching.Cache类对提高程序性能也非常有用,虽然利用Session或者Application也能实现在内存中保存数据,但是在Session中保存的数据只能被单个用户使用,而在Application中使用的数据如果不手动释放就会一直保存在内存当中,利用Cache就完全克服了上面的缺点。Cache类提供了强大的功能,允许自定义缓存项及缓存时间和优先级等,在服务器内存不够用时会自动较少使用的或者优先级比较低的项以释放内存。另外还可以指定缓存关联依赖项,如果缓存关联依赖项发生改变缓存项就会实效并从缓存中移除。比如可以将一个经常要读取的文件的内容缓存起来,并在文件上保留一个依赖项,一旦文件内容发生变化就会从内存中移除缓存的文件内容,可以再次从文件中重新读取文件内容到缓存中,这样就保证了得到的文件内容是最新的。

下面就是一个在ASP.NET使用Cache的例子,页面的设计部分代码如下:

使用Cache的例子
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值