第一篇:。NET 性能优化方法总结与字符串连接优化
.NET 性能优化方法总结
Ver 1.0
2009-1-20
目录
1.C#语言方面...............................................................................................................4
1.1 垃圾回收.......................................................................................................4
1.1.1 避免不必要的对象创建.....................................................................4 1.1.2 不要使用空析构函数 ★...................................................................4 1.1.3 实现 IDisposable 接口.....................................................................4
1.2 String 操作....................................................................................................5
1.2.1 使用 StringBuilder 做字符串连接...................................................5 1.2.2 避免不必要的调用 ToUpper 或 ToLower 方法...........................5 1.2.3 最快的空串比较方法.........................................................................6
1.3 多线程...........................................................................................................6
1.3.1 线程同步.............................................................................................6 1.3.2 使用 ThreadStatic 替代 NameDataSlot ★.....................................7 1.3.3 多线程编程技巧.................................................................................7
1.4 类型系统.......................................................................................................8
1.4.1 避免无意义的变量初始化动作.........................................................8 1.4.2 ValueType 和 ReferenceType...........................................................8 1.4.3 尽可能使用最合适的类型.................................................................9
1.5 异常处理.....................................................................................................10
1.5.1 不要吃掉异常★...............................................................................10 1.5.2 不要吃掉异常信息★.......................................................................10 1.5.3 避免不必要的抛出异常...................................................................10 1.5.4 避免不必要的重新抛出异常...........................................................10 1.5.5 捕获指定的异常,不要使用通用的System.Exception.................10 1.5.6 要在finally里释放占用的资源......................................................11
1.6 反射.............................................................................................................11
1.6.1 反射分类...........................................................................................12 1.6.2 动态创建对象...................................................................................12 1.6.3 动态方法调用...................................................................................12 1.6.4 推荐的使用原则...............................................................................12
1.7 基本代码技巧.............................................................................................13
1.7.1 循环写法...........................................................................................13 1.7.2 拼装字符串.......................................................................................13 1.7.3 避免两次检索集合元素...................................................................13 1.7.4 避免两次类型转换...........................................................................14 1.7.5为字符串容器声明常量,不要直接把字符封装在双引号“ ”里面。.....................................................................................................................14 1.7.6 用StringBuilder代替使用字符串连接符 “+”...............................14 1.7.7 避免在循环体里声明变量,...........................................................15
1.8 Hashtable......................................................................................................15 1.8.1 Hashtable机理...................................................................................15 1.8.2 使用HashTale代替其他字典集合类型的情形:......................16
1.9 避免使用ArrayList。..............................................................................16 1.10从XML对象读取数据.............................................................................17 1.11 避免使用递归调用和嵌套循环,...........................................................17 1.12 使用适当的Caching策略来提高性能...................................................17
2.Ado.Net....................................................................................................................17
2.1 应用Ado.net的一些思考原则..................................................................18 2.2 Connection...................................................................................................18
2.2.1 在方法中打开和关闭连接...............................................................18 2.2.2 显式关闭连接...................................................................................18 2.2.3 确保连接池启用...............................................................................19 2.2.4 不要缓存连接...................................................................................19
2.3 Command.....................................................................................................19
2.3.1 使用ExecuteScalar和ExecuteNonQuery.......................................19 2.3.2 使用Prepare.....................................................................................19 2.3.3 使用绑定变量 ★.............................................................................19
2.4 DataReader...................................................................................................20
2.4.1 显式关闭DataReader.......................................................................20 2.4.2 用索引号访问代替名称索引号访问属性.......................................20 2.4.3 使用类型化方法访问属性...............................................................20 2.4.4 使用多数据集...................................................................................20
2.5 DataSet.........................................................................................................21
2.5.1 利用索引加快查找行的效率...........................................................21 2.使用DataView......................................................................................21
3.ASP.NET..................................................................................................................21
3.1 减少往返行程(Reduce Round Trips)...................................................21 3.2 避免阻塞和长时间的作业.........................................................................22 3.3 使用缓存.....................................................................................................22 3.4 多线程.........................................................................................................22 3.5 系统资源.....................................................................................................23 3.6 页面处理.....................................................................................................23 3.7 ViewState.....................................................................................................23
4.JScript.......................................................................................................................24
4.1 JScript性能优化的基本原则.....................................................................24 4.2 JScript语言本身的优化.............................................................................24 4.3 DOM相关...................................................................................................27 4.4 其他.............................................................................................................28
1.C#语言方面
1.1 垃圾回收
垃圾回收解放了手工管理对象的工作,提高了程序的健壮性,但副作用就是程序代码可能对于对象创建变得随意。1.1.1 避免不必要的对象创建
由于垃圾回收的代价较高,所以C#程序开发要遵循的一个基本原则就是避免不必要的对象创建。以下列举一些常见的情形。
1.1.1.1 避免循环创建对象 ★
如果对象并不会随每次循环而改变状态,那么在循环中反复创建对象将带来性能损耗。高效的做法是将对象提到循环外面创建。
1.1.1.2 在需要逻辑分支中创建对象
如果对象只在某些逻辑分支中才被用到,那么应只在该逻辑分支中创建对象。
1.1.1.3 使用常量避免创建对象
程序中不应出现如 new Decimal(0)之类的代码,这会导致小对象频繁创建及回收,正确的做法是使用Decimal.Zero常量。我们有设计自己的类时,也可以学习这个设计手法,应用到类似的场景中。
1.1.1.4 使用StringBuilder做字符串连接
1.1.2 不要使用空析构函数 ★
如果类包含析构函数,由创建对象时会在 Finalize 队列中添加对象的引用,以保证当对象无法可达时,仍然可以调用到 Finalize 方法。垃圾回收器在运行期间,会启动一个低优先级的线程处理该队列。相比之下,没有析构函数的对象就没有这些消耗。如果析构函数为空,这个消耗就毫无意义,只会导致性能降低!因此,不要使用空的析构函数。
在实际情况中,许多曾在析构函数中包含处理代码,但后来因为种种原因被注释掉或者删除掉了,只留下一个空壳,此时应注意把析构函数本身注释掉或删除掉。1.1.3 实现 IDisposable 接口
垃圾回收事实上只支持托管内在的回收,对于其他的非托管资源,例如
Window GDI 句柄或数据库连接,在析构函数中释放这些资源有很大问题。原因是垃圾回收依赖于内在紧张的情况,虽然数据库连接可能已濒临耗尽,但如果内存还很充足的话,垃圾回收是不会运行的。
C#的 IDisposable 接口是一种显式释放资源的机制。通过提供 using 语句,还简化了使用方式(编译器自动生成 try...finally 块,并在 finally 块中调用 Dispose 方法)。对于申请非托管资源对象,应为其实现 IDisposable 接口,以保证资源一旦超出 using 语句范围,即得到及时释放。这对于构造健壮且性能优良的程序非常有意义!
为防止对象的 Dispose 方法不被调用的情况发生,一般还要提供析构函数,两者调用一个处理资源释放的公共方法。同时,Dispose 方法应调用
System.GC.SuppressFinalize(this),告诉垃圾回收器无需再处理 Finalize 方法了。
1.2 String 操作
1.2.1 使用 StringBuilder 做字符串连接
String 是不变类,使用 + 操作连接字符串将会导致创建一个新的字符串。如果字符串连接次数不是固定的,例如在一个循环中,则应该使用 StringBuilder 类来做字符串连接工作。因为 StringBuilder 内部有一个 StringBuffer,连接操作不会每次分配新的字符串空间。只有当连接后的字符串超出 Buffer 大小时,才会申请新的 Buffer 空间。典型代码如下:
StringBuilder sb = new StringBuilder(256);for(int i = 0;i < Results.Count;i ++){
sb.Append(Results[i]);}
如果连接次数是固定的并且只有几次,此时应该直接用 + 号连接,保持程序简洁易读。实际上,编译器已经做了优化,会依据加号次数调用不同参数个数的 String.Concat 方法。例如:String str = str1 + str2 + str3 + str4;
会被编译为 String.Concat(str1, str2, str3, str4)。该方法内部会计算总的 String 长度,仅分配一次,并不会如通常想象的那样分配三次。作为一个经验值,当字符串连接操作达到 10 次以上时,则应该使用 StringBuilder。
这里有一个细节应注意:StringBuilder 内部 Buffer 的缺省值为 16,这个值实在太小。按 StringBuilder 的使用场景,Buffer 肯定得重新分配。经验值一般用 256 作为 Buffer 的初值。当然,如果能计算出最终生成字符串长度的话,则应该按这个值来设定 Buffer 的初值。使用 new StringBuilder(256)就将 Buffer 的初始长度设为了256。1.2.2 避免不必要的调用 ToUpper 或 ToLower 方法
String是不变类,调用ToUpper或ToLower方法都会导致创建一个新的字符串。如果被频繁调用,将导致频繁创建字符串对象。这违背了前面讲到的“避免频繁创建对象”这一基本原则。例如,bool.Parse方法本身已经是忽略大小写的,调用时不要调用ToLower方法。
另一个非常普遍的场景是字符串比较。高效的做法是使用 Compare 方法,这个方法可以做大小写忽略的比较,并且不会创建新字符串。
例:
const string C_VALUE = “COMPARE”;
if(String.Compare(sVariable, C_VALUE, true)== 0)
{
Console.Write(“SAME”);
}
还有一种情况是使用 HashTable 的时候,有时候无法保证传递 key 的大小写是否符合预期,往往会把 key 强制转换到大写或小写方法。实际上 HashTable 有不同的构造形式,完全支持采用忽略大小写的 key: new HashTable(StringComparer.OrdinalIgnoreCase)。1.2.3 最快的空串比较方法
将String对象的Length属性与0比较是最快的方法:if(str.Length == 0)
其次是与String.Empty常量或空串比较:if(str == String.Empty)或if(str == “")
注:C#在编译时会将程序集中声明的所有字符串常量放到保留池中(intern pool),相同常量不会重复分配。
1.3 多线程
1.3.1 线程同步
线程同步是编写多线程程序需要首先考虑问题。C#为同步提供了 Monitor、Mutex、AutoResetEvent 和 ManualResetEvent 对象来分别包装 Win32 的临界区、互斥对象和事件对象这几种基础的同步机制。C#还提供了一个lock语句,方便使用,编译器会自动生成适当的 Monitor.Enter 和 Monitor.Exit 调用。
1.3.1.1 同步粒度
同步粒度可以是整个方法,也可以是方法中某一段代码。为方法指定 MethodImplOptions.Synchronized 属性将标记对整个方法同步。例如:
[MethodImpl(MethodImplOptions.Synchronized)] public static SerialManager GetInstance(){
if(instance == null){
instance = new SerialManager();}
return instance;}
通常情况下,应减小同步的范围,使系统获得更好的性能。简单将整个方法标记为同步不是一个好主意,除非能确定方法中的每个代码都需要受同步保护。
1.3.1.2 同步策略
使用 lock 进行同步,同步对象可以选择 Type、this 或为同步目的专门构造的成员变量。
避免锁定Type★
锁定Type对象会影响同一进程中所有AppDomain该类型的所有实例,这不仅可能导致严重的性能问题,还可能导致一些无法预期的行为。这是一个很不好的习惯。即便对于一个只包含static方法的类型,也应额外构造一个static的成员变量,让此成员变量作为锁定对象。
避免锁定 this
锁定 this 会影响该实例的所有方法。假设对象 obj 有 A 和 B 两个方法,其中 A 方法使用 lock(this)对方法中的某段代码设置同步保护。现在,因为某种原因,B 方法也开始使用 lock(this)来设置同步保护了,并且可能为了完全不同的目的。这样,A 方法就被干扰了,其行为可能无法预知。所以,作为一种良好的习惯,建议避免使用 lock(this)这种方式。
使用为同步目的专门构造的成员变量
这是推荐的做法。方式就是 new 一个 object 对象,该对象仅仅用于同步目的。
如果有多个方法都需要同步,并且有不同的目的,那么就可以为些分别建立几个同步成员变量。
1.3.1.4 集合同步
C#为各种集合类型提供了两种方便的同步机制:Synchronized 包装器和 SyncRoot 属性。
// Creates and initializes a new ArrayList ArrayList myAL = new ArrayList();myAL.Add(” The “);myAL.Add(” quick “);myAL.Add(” brown “);myAL.Add(” fox “);
// Creates a synchronized wrapper around the ArrayList ArrayList mySyncdAL = ArrayList.Synchronized(myAL);
调用 Synchronized 方法会返回一个可保证所有操作都是线程安全的相同集合对象。考虑 mySyncdAL[0] = mySyncdAL[0] + ”test“ 这一语句,读和写一共要用到两个锁。一般讲,效率不高。推荐使用 SyncRoot 属性,可以做比较精细的控制。
1.3.2 使用 ThreadStatic 替代 NameDataSlot ★ 存取 NameDataSlot 的 Thread.GetData 和 Thread.SetData 方法需要线程同步,涉及两个锁:一个是 LocalDataStore.SetData 方法需要在 AppDomain 一级加锁,另一个是 ThreadNative.GetDomainLocalStore 方法需要在 Process 一级加锁。如果一些底层的基础服务使用了 NameDataSlot,将导致系统出现严重的伸缩性问题。
规避这个问题的方法是使用 ThreadStatic 变量。示例如下:
public sealed class InvokeContext {
[ThreadStatic]
private static InvokeContext current;
private Hashtable maps = new Hashtable();}
1.3.3 多线程编程技巧
1.3.3.1 使用 Double Check 技术创建对象
internal IDictionary KeyTable { get {
if(this._keyTable == null){
lock(base._lock){
if(this._keyTable == null){
this._keyTable = new Hashtable();} } }
return this._keyTable;} }
创建单例对象是很常见的一种编程情况。一般在 lock 语句后就会直接创建对象了,但这不够安全。因为在 lock 锁定对象之前,可能已经有多个线程进入到了第一个 if 语句中。如果不加第二个 if 语句,则单例对象会被重复创建,新的实例替代掉旧的实例。如果单例对象中已有数据不允许被破坏或者别的什么原因,则应考虑使用 Double Check 技术。
1.4 类型系统 1.4.1 避免无意义的变量初始化动作
CLR保证所有对象在访问前已初始化,其做法是将分配的内存清零。因此,不需要将变量重新初始化为0、false或null。
需要注意的是:方法中的局部变量不是从堆而是从栈上分配,所以C#不会做清零工作。如果使用了未赋值的局部变量,编译期间即会报警。不要因为有这个印象而对所有类的成员变量也做赋值动作,两者的机理完全不同!1.4.2 ValueType 和 ReferenceType
1.4.2.1 以引用方式传递值类型参数
值类型从调用栈分配,引用类型从托管堆分配。当值类型用作方法参数时,默认会进行参数值复制,这抵消了值类型分配效率上的优势。作为一项基本技巧,以引用方式传递值类型参数可以提高性能。
1.4.2.2 为 ValueType 提供 Equals 方法
.net 默认实现的 ValueType.Equals 方法使用了反射技术,依靠反射来获得所有成员变量值做比较,这个效率极低。如果我们编写的值对象其 Equals 方法要被用到(例如将值对象放到 HashTable 中),那么就应该重载 Equals 方法。
public struct Rectangle {
public double Length;public double Breadth;
public override bool Equals(object ob){
if(ob is Rectangle)
return Equels((Rectangle)ob))else
return false;}
private bool Equals(Rectangle rect){
return this.Length == rect.Length && this.Breadth == rect.Breach;} }
1.4.2.3 避免装箱和拆箱
C#可以在值类型和引用类型之间自动转换,方法是装箱和拆箱。装箱需要从堆上分配对象并拷贝值,有一定性能消耗。如果这一过程发生在循环中或是作为底层方法被频繁调用,则应该警惕累计的效应。
一种经常的情形出现在使用集合类型时。例如:
ArrayList al = new ArrayList();for(int i = 0;i < 1000;i ++){ al.Add(i);// Implicitly boxed because Add()takes an object }
int f =(int)al[ 0 ];// The element is unboxed
但是得当心!如果你像使用引用类型那么频繁的使用一个值类型的话,值类型的优势会很快被耗尽。比如,把一个值类型压到一个含有对象类型的群集。这叫做装箱,很耗用处理器周期,尤其是当你的代码在把它作为值(对它进行数学运算)和把它作为引用之间来回运行时。
1.4.3 尽可能使用最合适的类型
• 尽可能使用最合适的类型来描述数据,从而减少类型转换。
• 使用泛型来创建群集和其它的数据结构,这样,在运行时,它们就可以被实例化来存储刚好合适的类型。这节省了装箱/拆箱和类型转换的时间。
• 在C#中使用as,而不是is。关键字is用来查看引用是否可以被作为某个具体的类型,但是并不返回转换到这个类型的引用。所以,通常当你从is获得一个正的结果时,你首先应该cast——有效地执行两次cast。采用as关键词时,如果可用,则返回cast为新类型的引用;否则返回null。你可以查看null然后做你喜欢做的事情。整体来说,As方法要比is方法快50%。
1.5 异常处理
异常也是现代语言的典型特征。与传统检查错误码的方式相比,异常是强制性的(不依赖于是否忘记了编写检查错误码的代码)、强类型的、并带有丰富的异常信息(例如调用栈)。1.5.1 不要吃掉异常★
关于异常处理的最重要原则就是:不要吃掉异常。这个问题与性能无关,但对于编写健壮和易于排错的程序非常重要。这个原则换一种说法,就是不要捕获那些你不能处理的异常。
吃掉异常是极不好的习惯,因为你消除了解决问题的线索。一旦出现错误,定位问题将非常困难。除了这种完全吃掉异常的方式外,只将异常信息写入日志文件但并不做更多处理的做法也同样不妥。1.5.2 不要吃掉异常信息★
有些代码虽然抛出了异常,但却把异常信息吃掉了。
为异常披露详尽的信息是程序员的职责所在。如果不能在保留原始异常信息含义的前提下附加更丰富和更人性化的内容,那么让原始的异常信息直接展示也要强得多。千万不要吃掉异常。1.5.3 避免不必要的抛出异常 抛出异常和捕获异常属于消耗比较大的操作,在可能的情况下,应通过完善程序逻辑避免抛出不必要不必要的异常。与此相关的一个倾向是利用异常来控制处理逻辑。尽管对于极少数的情况,这可能获得更为优雅的解决方案,但通常而言应该避免。1.5.4 避免不必要的重新抛出异常
如果是为了包装异常的目的(即加入更多信息后包装成新异常),那么是合理的。但是有不少代码,捕获异常没有做任何处理就再次抛出,这将无谓地增加一次捕获异常和抛出异常的消耗,对性能有伤害。
1.5.5 捕获指定的异常,不要使用通用的System.Exception.//避免
try
{
}
catch(Exception exc)
{
}
//推荐
try
{
}
catch(System.NullReferenceException exc)
{
}
catch(System.ArgumentOutOfRangeException exc)
{
}
catch(System.InvalidCastException exc)
{
}
1.5.6 要在finally里释放占用的资源
使用Try...catch...finally时,要在finally里释放占用的资源如连接,文件流等,不然在Catch到错误后占用的资源不能释放。
try
{
...}
catch
{...}
finally
{
conntion.close()
}
1.6 反射
反射是一项很基础的技术,它将编译期间的静态绑定转换为延迟到运行期间的动态绑定。在很多场景下(特别是类框架的设计),可以获得灵活易于扩展的架构。但带来的问题是与静态绑定相比,动态绑定会对性能造成较大的伤害。1.6.1 反射分类
type comparison :类型判断,主要包括 is 和 typeof 两个操作符及对象实例上的 GetType 调用。这是最轻型的消耗,可以无需考虑优化问题。注意 typeof 运算符比对象实例上的 GetType 方法要快,只要可能则优先使用 typeof 运算符。
member enumeration : 成员枚举,用于访问反射相关的元数据信息,例如Assembly.GetModule、Module.GetType、Type对象上的IsInterface、IsPublic、GetMethod、GetMethods、GetProperty、GetProperties、GetConstructor调用等。尽管元数据都会被CLR缓存,但部分方法的调用消耗仍非常大,不过这类方法调用频度不会很高,所以总体看性能损失程度中等。
member invocation:成员调用,包括动态创建对象及动态调用对象方法,主要有Activator.CreateInstance、Type.InvokeMember等。1.6.2 动态创建对象
C#主要支持 5 种动态创建对象的方式:
1.Type.InvokeMember
2.ContructorInfo.Invoke
3.Activator.CreateInstance(Type)
4.Activator.CreateInstance(assemblyName, typeName)
5.Assembly.CreateInstance(typeName)
最快的是方式 3,与 Direct Create 的差异在一个数量级之内,约慢 7 倍的水平。其他方式,至少在 40 倍以上,最慢的是方式 4,要慢三个数量级。1.6.3 动态方法调用
方法调用分为编译期的早期绑定和运行期的动态绑定两种,称为Early-Bound Invocation和Late-Bound Invocation。Early-Bound Invocation可细分为Direct-call、Interface-call和Delegate-call。Late-Bound Invocation主要有Type.InvokeMember和MethodBase.Invoke,还可以通过使用LCG(Lightweight Code Generation)技术生成IL代码来实现动态调用。
从测试结果看,相比Direct Call,Type.InvokeMember要接近慢三个数量级;MethodBase.Invoke虽然比Type.InvokeMember要快三倍,但比Direct Call仍慢270倍左右。可见动态方法调用的性能是非常低下的。我们的建议是:除非要满足特定的需求,否则不要使用!1.6.4 推荐的使用原则
模式
1. 如果可能,则避免使用反射和动态绑定
2. 使用接口调用方式将动态绑定改造为早期绑定
3. 使用Activator.CreateInstance(Type)方式动态创建对象
4. 使用typeof操作符代替GetType调用
反模式
1. 在已获得Type的情况下,却使用Assembly.CreateInstance(type.FullName)
1.7 基本代码技巧
这里描述一些应用场景下,可以提高性能的基本代码技巧。对处于关键路径的代码,进行这类的优化还是很有意义的。普通代码可以不做要求,但养成一种好的习惯也是有意义的。1.7.1 循环写法
可以把循环的判断条件用局部变量记录下来。局部变量往往被编译器优化为直接使用寄存器,相对于普通从堆或栈中分配的变量速度快。如果访问的是复杂计算属性的话,提升效果将更明显。for(int i = 0, j = collection.GetIndexOf(item);i < j;i++)
需要说明的是:这种写法对于CLR集合类的Count属性没有意义,原因是编译器已经按这种方式做了特别的优化。1.7.2 拼装字符串
拼装好之后再删除是很低效的写法。有些方法其循环长度在大部分情况下为1,这种写法的低效就更为明显了:
public static string ToString(MetadataKey entityKey){
string str = ”“;
object [] vals = entityKey.values;for(int i = 0;i < vals.Length;i ++){
str += ” , “ + vals[i].ToString();}
return str == ”“ ? ”“ : str.Remove(0 , 1);}
推荐下面的写法:
if(str.Length == 0)str = vals[i].ToString();else
str += ” , “ + vals[i].ToString();其实这种写法非常自然,而且效率很高,完全不需要用个Remove方法绕来绕去。1.7.3 避免两次检索集合元素
获取集合元素时,有时需要检查元素是否存在。通常的做法是先调用ContainsKey(或Contains)方法,然后再获取集合元素。这种写法非常符合逻辑。
但如果考虑效率,可以先直接获取对象,然后判断对象是否为null来确定元素是否存在。对于Hashtable,这可以节省一次GetHashCode调用和n次Equals比较。
如下面的示例:
public IData GetItemByID(Guid id){
IData data1 = null;
if(this.idTable.ContainsKey(id.ToString()){
data1 = this.idTable[id.ToString()] as IData;}
return data1;}
其实完全可用一行代码完成:return this.idTable[id] as IData;1.7.4 避免两次类型转换
考虑如下示例,其中包含了两处类型转换:
if(obj is SomeType){
SomeType st =(SomeType)obj;st.SomeTypeMethod();}
效率更高的做法如下:
SomeType st = obj as SomeType;if(st!= null){
st.SomeTypeMethod();}
1.7.5为字符串容器声明常量,不要直接把字符封装在双引号” “里面。
//避免
//
MyObject obj = new MyObject();
obj.Status = ”ACTIVE“;
//推荐
const string C_STATUS = ”ACTIVE“;
MyObject obj = new MyObject();
obj.Status = C_STATUS;
1.7.6 用StringBuilder代替使用字符串连接符
//避免
String sXML = ” “;
sXML += ”“;
sXML += ”Data“;
sXML += ”“;
sXML += ”“;
//推荐
StringBuilder sbXML = new StringBuilder();
sbXML.Append(” “);
sbXML.Append(”“);
sbXML.Append(”Data“);
sbXML.Append(”“);
sbXML.Append(”“);
1.7.7 避免在循环体里声明变量,应该在循环体外声明变量,在循环体里初始化。
//避免
for(int i=0;i<10;i++)
+”
“
{
SomeClass objSC = new SomeClass();} //推荐
SomeClass objSC = null;for(int i=0;i<10;i++){
objSC = new SomeClass();)
1.8 Hashtable 1.8.1 Hashtable机理
Hashtable是一种使用非常频繁的基础集合类型。需要理解影响Hashtable的效率有两个因素:一是散列码(GetHashCode方法),二是等值比较(Equals方法)。Hashtable首先使用键的散列码将对象分布到不同的存储桶中,随后在该特定的存储桶中使用键的Equals方法进行查找。
良好的散列码是第一位的因素,最理想的情况是每个不同的键都有不同的散列码。Equals方法也很重要,因为散列只需要做一次,而存储桶中查找键可能需要做多次。从实际经验看,使用Hashtable时,Equals方法的消耗一般会占到一半以上。
System.Object类提供了默认的GetHashCode实现,使用对象在内存中的地址作为散列码。我们遇到过一个用Hashtable来缓存对象的例子,每次根据传递的OQL表达式构造出一个ExpressionList对象,再调用QueryCompiler的方法编译得到CompiledQuery对象。以ExpressionList对象和CompiledQuery对象作为键值对存储到Hashtable中。ExpressionList对象没有重载GetHashCode实现,其超类ArrayList也没有,这样最后用的就是System.Object类的GetHashCode实现。由于ExpressionList对象会每次构造,因此它的HashCode每次都不同,所以这个CompiledQueryCache根本就没有起到预想的作用。这个小小的疏漏带来了重大的性能问题,由于解析OQL表达式频繁发生,导致CompiledQueryCache不断增长,造成服务器内存泄漏!解决这个问题的最简单方法就是提供一个常量实现,例如让散列码为常量0。虽然这会导致所有对象汇聚到同一个存储桶中,效率不高,但至少可以解决掉内存泄漏问题。当然,最终还是会实现一个高效的GetHashCode方法的。
以上介绍这些Hashtable机理,主要是希望大家理解:如果使用Hashtable,你应该检查一下对象是否提供了适当的GetHashCode和Equals方法实现。否则,有可能出现效率不高或者与预期行为不符的情况。
1.8.2 使用HashTale代替其他字典集合类型的情形:
其他字典集合类型(如StringDictionary,NameValueCollection,HybridCollection),存放少量数据的时候可以使用HashTable。很多非泛型集合类都有对应的泛型集合类,下面是常用的非泛型集合类以及对应的泛型集合类:
非泛型集合类 泛型集合类
ArrayList List
HashTable DIctionary
SortedList SortedList
我们用的比较多的非泛型集合类主要有 ArrayList类 和 HashTable类。我们经常用HashTable 来存储将要写入到数据库或者返回的信息,在这之间要不断的进行类型的转化,增加了系统装箱和拆箱的负担,如果我们操纵的数据类型相对确定的化
用 Dictionary
1.9 避免使用ArrayList。
因为任何对象添加到ArrayList都要封箱为System.Object类型,从ArrayList取出数据时,要拆箱回实际的类型。建议使用自定义的集合类型代替ArrayList。.net 2.0提供了一个新的类型,叫泛型,这是一个强类型,使用泛型集合就可以避免了封箱和拆箱的发生,提高了性能。
1.10从XML对象读取数据
如果只是从XML对象读取数据,用只读的XPathDocument代替XMLDocument,可以提高性能
//避免
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+”~“;
}
}
1.11 避免使用递归调用和嵌套循环,使用他们会严重影响性能,在不得不用的时候才使用。
1.12 使用适当的Caching策略来提高性能
2.Ado.Net
2.1 应用Ado.net的一些思考原则
1.根据数据使用的方式来设计数据访问层 2.缓存数据,避免不必要的操作 3.使用服务帐户进行连接 4.必要时申请,尽早释放 5.关闭可关闭的资源 6.减少往返
7.仅返回需要的数据 8.选择适当的事务类型 9.使用存储过程
2.2 Connection 数据库连接是一种共享资源,并且打开和关闭的开销较大。Ado.net默认启用了连接池机制,关闭连接不会真的关闭物理连接,而只是把连接放回到连接池中。因为池中共享的连接资源始终是有限的,如果在使用连接后不尽快关闭连接,那么就有可能导致申请连接的线程被阻塞住,影响整个系统的性能表现。2.2.1 在方法中打开和关闭连接
这个原则有几层含义:
1.主要目的是为了做到必要时申请和尽早释放
2.不要在类的构造函数中打开连接、在析构函数中释放连接。因为这将依赖于垃圾回收,而垃圾回收只受内存影响,回收时机不定
3.不要在方法之间传递连接,这往往导致连接保持打开的时间过长
这里强调一下在方法之间传递连接的危害:曾经在压力测试中遇到过一个测试案例,当增大用户数的时候,这个案例要比别的案例早很久就用掉连接池中的所有连接。经分析,就是因为A方法把一个打开的连接传递到了B方法,而B方法又调用了一个自行打开和关闭连接的C方法。在A方法的整个运行期间,它至少需要占用两条连接才能够成功工作,并且其中的一条连接占用时间还特别长,所以造成连接池资源紧张,影响了整个系统的可伸缩性!
2.2.2 显式关闭连接
Connection对象本身在垃圾回收时可以被关闭,而依赖垃圾回收是很不好的策略。推荐使用using语句显式关闭连接,如下例:
using(SqlConnection conn = new SqlConnection(connString)){
conn.Open();
} // Dispose is automatically called on the conn variable here
2.2.3 确保连接池启用
Ado.net是为每个不同的连接串建立连接池,因此应该确保连接串不会出现与具体用户相关的信息。另外,要注意连接串是大小写敏感的。2.2.4 不要缓存连接
例如,把连接缓存到Session或Application中。在启用连接池的情况下,这种做法没有任何意义。
2.3 Command
2.3.1 使用ExecuteScalar和ExecuteNonQuery
如果想返回像Count(*)、Sum(Price)或Avg(Quantity)那样的单值,可以使用ExecuteScalar方法。ExecuteScalar返回第一行第一列的值,将结果集作为标量值返回。因为单独一步就能完成,所以ExecuteScalar不仅简化了代码,还提高了性能。
使用不返回行的SQL语句时,例如修改数据(INSERT、UPDATE或DELETE)或仅返回输出参数或返回值,请使用ExecuteNonQuery。这避免了用于创建空DataReader的任何不必要处理。2.3.2 使用Prepare
当需要重复执行同一SQL语句多次,可考虑使用Prepare方法提升效率。需要注意的是,如果只是执行一次或两次,则完全没有必要。例如:
cmd.CommandText = ”insert into Table1(Col1, Col2)values(@val1, @val2)“;
cmd.Parameters.Add(”@val1“, SqlDbType.Int, 4, ”Col1“);cms.Parameters.Add(”@val2“, SqlDbType.NChar, 50, ”Col2“);
cmd.Parameters[0].Value = 1;
cmd.Parameters[1].Value = ”XXX“;cmd.Prepare();
cmd.ExecuteNonQuery();
cmd.Parameters[0].Value = 2;
cmd.Parameters[1].Value = ”YYY“;cmd.ExecuteNonQuery();
cmd.Parameters[0].Value = 3;
cmd.Parameters[1].Value = ”ZZZ“;cmd.ExecuteNonQuery();
2.3.3 使用绑定变量 ★
SQL语句需要先被编译成执行计划,然后再执行。如果使用绑定变量的方式,那么这个执行计划就可以被后续执行的SQL语句所复用。而如果直接把参数合并到了SQL语句中,由于参数值千变万化,执行计划就难以被复用了。例如上面Prepare一节给出的示例,如果把参数值直接写到insert语句中,那么上面的四次调用将需要编译四次执行计划。
为避免这种情况造成性能损失,要求一律使用绑定变量方式。
2.4 DataReader
DataReader最适合于访问只读的单向数据集。与DataSet不同,数据集并不全部在内存中,而是随不断发出的read请求,一旦发现数据缓冲区中的数据均被读取,则从数据源传输一个数据缓冲区大小的数据块过来。另外,DataReader保持连接,DataSet则与连接断开。2.4.1 显式关闭DataReader
与连接类似,也需要显式关闭DataReader。另外,如果与DataReader关联的Connection仅为DataReader服务的话,可考虑使用Command对象的ExecuteReader(CommandBehavior.CloseConnection)方式。这可以保证当DataReader关闭时,同时自动关闭Connection。2.4.2 用索引号访问代替名称索引号访问属性
从Row中访问某列属性,使用索引号的方式比使用名称方式有细微提高。如果会被频繁调用,例如在循环中,那么可考虑此类优化。示例如下:
cmd.CommandText = ”select Col1, Col2 from Table1“;SqlDataReader dr = cmd.ExecuteReader();
int col1 = dr.GetOrdinal(”Col1“);int col2 = dr.GetOrdinal(”Col2“);
while(dr.Read()){
Console.WriteLine(dr[col1] + ”_“ + dr[col2]);}
2.4.3 使用类型化方法访问属性
从Row中访问某列属性,用GetString、GetInt32这种显式指明类型的方法,其效率较通用的GetValue方法有细微提高,因为不需要做类型转换。2.4.4 使用多数据集
部分场景可以考虑一次返回多数据集来降低网络交互次数,提升效率。示例如下:
cmd.CommandText = ”StoredProcedureName“;// The stored procedure returns multiple result sets.SqlDataReader dr = cmd.ExecuteReader();
while(dr.read())// read first result set
dr.NextResult();
while(dr.read())//
2.5 DataSet
2.5.1 利用索引加快查找行的效率
如果需要反复查找行,建议增加索引。有两种方式:
1.设置DataTable的PrimaryKey
适用于按PrimaryKey查找行的情况。注意此时应调用DataTable.Rows.Find方法,一般惯用的Select方法不能利用索引。2.使用DataView
适用于按Non-PrimaryKey查找行的情况。可为DataTable创建一个DataView,并通过SortOrder参数指示建立索引。此后使用Find或FindRows查找行。
3.ASP.NET
3.1 减少往返行程(Reduce Round Trips)
使用下面的方法可以减少Web服务器和Browser之间的往返行程:
1.为Browser启用缓存
如果呈现的内容是静态的或变化周期较长,应启用Browser缓存,避免发出冗余的http请求。2.缓冲页面输出
如果可能,则尽量缓冲页面输出,处理结束后再一次传送到客户端,这可以避免频繁传递小块内容所造成的多次网络交互。由于这种方式在页面处理结束之前客户端无法看到页面内容,因此如果一个页面的尺寸较大的话,可考虑使用Response.Flush方法。该方法强制输出迄今为止在缓冲区中的内容,你应当采用合理的算法控制调用Response.Flush方法的次数。
3.使用Server.Transfer重定向请求
使用Server.Transfer方法重定向请求优于Response.Redirect方法。原因是Response.Redirect会向Broswer回送一个响应头,在响应头中指出重定向的URL,之后Brower使用新的URL重新发出请求。而Server.Transfer方法直接是一个简单的服务端调用,完全没有这些开销!
需要注意Server.Transfer有局限性:第一,它会跳过安全检查;第二,只适用于在同一Web应用内的页面间跳转。
3.2 避免阻塞和长时间的作业 如果需要运行阻塞或长时间运行的操作,可以考虑使用异步调用的机制,以便Web服务器能够继续处理其它的请求。
1.使用异步方式调用Web服务和远程对象
只要有可能就要避免在请求的处理过程中对Web服务和远程对象的同步调用,因为它占用的是的ASP.NET 线程池中的工作线程,这将直接影响Web服务器响应其它请求的能力。
2.考虑给不需要返回值的Web方法或远程对象的方法添加OneWay属性
这种模式能让Web Server调用之后就立即返回。可根据实际情况决定是否使用这种方法。
3.使用工作队列
将作业提交到服务器上的工作队列中。客户端通过发送请求来轮询作业的执行结果。
3.3 使用缓存
缓存能在很大程度上决定ASP.NET应用的最终性能。Asp.net支持页面输出缓存和页面部分缓存,并提供Cache API,供应用程序缓存自己的数据。是否使用缓存可考虑下面的要点:
1.识别创建与访问代价较大的数据
2.评估需要缓存数据的易变性
3.评估数据的使用频次
4.将要缓存数据中易变数据和不变数据分离,只缓存不变数据
5.选择合适的缓存机制(除Asp.net Cache外,Application state和Session state也可以作为缓存使用)
3.4 多线程
1.避免在请求处理过程中创建线程
在执行请求的过程中创建线程是一种代价较大的操作,会严重影响Web Server的性能。如果后续的操作必须用线程完成,建议通过thread pool来创建/管理线程。
2.不要依赖线程数据槽或线程静态变量
由于执行请求的线程是ASP.NET thread pool中的工作线程,同一个Client的两次请求不一定由相同的线程来处理。
3.避免阻塞处理请求的线程
参考”避免阻塞和长时间的作业“小节。
4.避免异步调用
这和1的情况类似。异步调用会导致创建新的线程,增加服务器的负担。所以,如果没有并发的作业要执行,就不要执行异步调用。
3.5 系统资源
1.考虑实现资源池以提升性能
2.明确地调用Dispose或Close释放系统资源 3.不要缓存或长时间占用资源池中的资源 4.尽可能晚的申请,尽可能早的释放
3.6 页面处理
1.尽量减小Page的尺寸
包括缩短控件的名称、CSS的class的名称、去掉无谓空行和空格、禁用不需要的ViewState
2.启用页面输出的缓冲区(Buffer)
如果Buffer的机制被关闭,可以用下面的方法打开。
使用程序打开页面输出缓存:
Response.BufferOutput = true;
使用@Page开关打开页面输出缓冲机制:
<%@ Page Buffer = ”true“ %>
使用Web.config或Machine.config配置文件的
节点:
3.利用Page.IsPostBack优化页面输出
4.通过分离页面的不同的内容,来提高缓存效率和减少呈现的时间
5.优化复杂和代价较大的循环
6.合理利用客户端的计算资源,将一些操作转移到客户端进行
3.7 ViewState
ViewState是Asp.net为服务端控件在页面回传之间跟踪状态信息而设计的一种机制。
1.关闭ViewState
如果不需要跟踪页面状态,例如页面不会 回传(PostBack)、不需要处理服务端控件事件或者每次页面刷新时都会重新计算控件内容,那么就不需要用ViewState来记录页面状态了。可以对特定的WebControl设置EnableViewState属性,也可以在页面一级设置:
<%@ Page EnableViewState=”false“ %>
2.在恰当的时间点初始化控件属性
ASP.NET的控件在执行构造函数、初始化的期间设置的属性不会被跟踪变化;而在初始化阶段之后对属性的修改都会被跟踪,并最终记录到IE页面的__VIEWSTATE之中。所以,选择合理的初始化控件属性的执行点,能有效的减小页面尺寸。
3.谨慎选择放到ViewState中的内容
放到ViewState中的内容会被序列化/反序列化,Asp.net为String、Integer、Boolean等基本类型的序列化做了优化,如果Array、ArrayList、HashTable存储的是基本类型效率也较高,但其它类型则需要提供类型转换器(Type Converter),否则将使用代价昂贵的二进制序列化程序。
4.JScript
4.1 JScript性能优化的基本原则
1.尽可能少地减少执行次数。毕竟对解释语言来说,每一个执行步骤,都需要和解释引擎做一次交互。
2.尽可能使用语言内置的功能,比如串链接。
3.尽可能使用系统提供的API来进行优化。因为这些API是编译好的二进制代码,执行效率很高。
4.书写最正确的代码。容错功能是要付出性能代价的。
4.2 JScript语言本身的优化
4.2.1 变量
1.尽量使用局部变量。
因为全局变量其实是全局对象的成员,而局部变量在栈上定义,优先查找,性能相对于全局变量要高。
2.尽量在一个语句中做定义变量和赋值。
3.省略不必要的变量定义。
如果变量的定义可以被一个常量替代,就直接使用常量。
4.使用Object语法对对象赋值。Object的赋值语法在操作复杂对象时效率更高。
例如,可以将下面的代码:
car = new Object();car.make = ”Honda“;car.model = ”Civic“;
car.transmission = ”manual“;car.miles = 100000;
car.condition = ”needs work“;替换成:
car = {
make: ”Honda“, model: ”Civic“,transmission: ”manual“, miles: 100000,condition: ”needs work“ }
4.2.2 对象缓存
1.缓存对象查找的中间结果。
因为JavaScript的解释性,所以a.b.c.d.e,需要进行至少4次查询操作,先检查a再检查a中的b,再检查b中的c,如此往下。所以如果这样的表达式重复出现,只要可能,应该尽量少出现这样的表达式,可以利用局部变量,把它放入一个临时的地方进行查询。
2.缓存创建时间较长的对象。
自定义高级对象和Date、RegExp对象在构造时都会消耗大量时间。如果可以复用,应采用缓存的方式。
4.2.3 字符串操作
1.使用”+=“ 追加字符串,使用”+“来连接字符串。
如果是追加字符串,最好使用s+=anotherStr操作,而不是要使用s=s+anotherStr。
如果要连接多个字符串,应该使用”+“,如:
s+=a;
s+=b;
s+=c;
应该写成
s+=a + b + c;
2.连接大量的字符串,应使用Array的join方法。如果是收集字符串,最好使用JavaScript数组缓存,最后使用join方法连接起来,如下:
var buf = new Array();for(var i = 0;i < 100;i++){
buf.push(i.toString());}
var all = buf.join(”“);
4.2.4 类型转换
1.使用Math.floor()或者Math.round()将浮点数转换成整型。
浮点数转换成整型,这个更容易出错,很多人喜欢使用parseInt(),其实parseInt()是用于将字符串转换成数字,而不是浮点数和整型之间的转换,我们应该使用Math.floor()或者Math.round()。
对象查找中的问题不一样,Math是内部对象,所以Math.floor()其实并没有多少查询方法和调用的时间,速度是最快的。
2.自定义的对象,推荐定义和使用toString()方法来进行类型转换。
对于自定义的对象,如果定义了toString()方法来进行类型转换的话,推荐显式调用toString()。因为内部的操作在尝试所有可能性之后,会尝试对象的toString()方法尝试能否转化为String,所以直接调用这个方法效率会更高。
4.2.5 循环的优化
1.尽可能少使用for(in)循环。
在JavaScript中,我们可以使用for(;;),while(),for(in)三种循环,事实上,这三种循环中for(in)的效率极差,因为他需要查询散列键,只要可以就应该尽量少用。
2.预先计算collection的length。
如:将for(var i = 0;i < collection.length;i++)
替换成:for(var i = 0, len = collection.length;i < len;i++)
效果会更好,尤其是在大循环中。
3.尽量减少循环内的操作。
循环内的每个操作,都会被放大为循环次数的倍数。所以,大循环内微小的改进,在性能的整体提升上都是可观的。
4.使用循环替代递归。
相比循环,递归的效率更差一些。递归的优点是在形式上更自然一些。所以,在不影响代码的维护性的前提下,用循环替代递归。
4.2.6 其它方面
1.尽量使用语言内置的语法。
”var arr = [„];“和”var arr = new Array(„);“是等效的,但是前者的效能优于后者。同样,”var foo = {};“的方式也比”var foo = new Object();“快;”var reg = /../;“要比”var reg=new RegExp()“快。
2.尽量不要使用eval。
使用eval,相当于在运行时再次调用解释引擎,对传入的内容解释运行,需要消耗大量时间。
3.使用prototype代替closure。
使用closure在性能和内存消耗上都是不利的。如果closure使用量过大,这就会成为一个问题。所以,尽量将:
this.methodFoo = function()
替换成:
MyClass.protoype.methodFoo = function()
和closure存在于对象实例之中不同,prototype存在于类中,被该类的所有的对象实例共享。
4.避免使用with语句。
With语句临时扩展对象查找的范围,节省了文字的录入时间,但付出了更多的执行时间。因为每个给出的名称都要在全局范围查找。所以,可以将下面的代码:
with(document.formname){
field1.value = ”one“;field2.value = ”two“;}
变更为:
var form = document.formname;form.field1.value = ”one“;form.field2.value = ”two“;
4.3 DOM相关
4.3.1 创建DOM节点
相比较通过document.write来给页面生成内容,找一个容器元素(比如指定一个div或者span)并设置他们的innerHTML效率更高。而设置innerHTML的方式比通过createElement方法创建节点的效率更高。事实上,设置元素的innerHTML是创建节点效率最高的一种方式。
如果必须使用createElement方法,而如果文档中存在现成的样板节点,应该是用cloneNode()方法。因为使用createElement()方法之后,你需要设置多次元素的属性,使用cloneNode()则可以减少属性的设置次数。同样,如果需要创建很多元素,应该先准备一个样板节点。
4.3.2 离线操作大型的DOM树
在添加一个复杂的DOM树时,可以先构造,构造结束后再将其添加到DOM数的适当节点。这能够节省界面刷新的时间。
同样,在准备编辑一个复杂的树时,可以先将树从DOM树上删除,等编辑结束后再添加回来。
4.3.3 对象查询
使用[”“]查询要比.item()更快。调用.item()增加了一次查询和函数的调用。
4.3.4 定时器
如果针对的是不断运行的代码,不应该使用setTimeout,而应该用setInterval。setTimeout每次要重新设置一个定时器。
4.4 其他
1.尽量减小文件尺寸。
将JScript文件中无关的空行、空格、注释去掉,有助于减小JS文件的尺寸,提高下载的时间。(可以通过工具来支持代码发布)
2.尽量不要在同一个Page内同时引用JScript和VBScript引擎
3.将Page内的JScript移入到单独的JS文件中。
4.将Page内的JScript放置在Page的最下面,有助于提高页面的响应速度。
5.利用cache,减少JScript文件的下载次数
6.在HTML内书写JScript文件的URL时,注意统一大小写。这样可以利用前面URL缓存的文件。
C# 性能优化——三种字符串拼接效率
2011年04月07日 星期四 17:56 字符串拼接主要包括三类:+,String.Format(),StringBuilder.Append()1)对于少量固定的字符串拼接,如string s= ”a“ + ”b“ + ”c“,系统会优化成s= String.Concat(”a“,”b“,”c“),不会新建多个字符串。
如果写成string s=”a“;s +=”b“;s+=”c“;则会创建三个新的字符串。2)String.Format的源代码: public static String Format(IFormatProvider provider, String format, params Object[] args){ if(format == null || args == null)throw new ArgumentNullException((format==null)?”format“:”args“);StringBuilder sb = new StringBuilder(format.Length + args.Length * 8);sb.AppendFormat(provider,format,args);return sb.ToString();} 可见,它和StringBuilder有着相似的效率,比用“+”的拼接方式高效,并且代码易于阅读。
string s= String.Format(”{0}{1}{2}“,”a“,”b“,”c");3)StringBuilder可以指定内存空间的容量,但可能需要进行数据类型转化。字符串较少时,可以使用String.Format()代替。
4)少量的字符串操作时,可以使用“+”或者String.Format();大量的字符串操作时,比如在循环体内,必须使用StringBuilder.Append()。
第二篇:(敏捷开发).NET性能优化方面的总结.docx
(敏捷开发).NET性能优化方面的总结
一、SqlDataRead和Dataset的选择
Sqldataread优点:读取数据非常快。如果对返回的数据不需做大量处理的情况下,建议使用SqlDataReader,其性能要比datset好很多。缺点:直到数据读完才可close掉于数据库的连接。(SqlDataReader读数据是快速向前的。SqlDataReader类提供了一种读取从 SQL Server 数据库检索的只进数据流的方法。它使用 SQL Server 的本机网络数据传输格式从数据库连接直接读取数据。DataReader需及时显式的close。可及时的释放对数据的连接。)
Dataset是把数据读出,缓存在内存中。缺点:对内存的占用较高。如果对返回的数据需做大量的处理用Dataset比较好些可以减少对数据库的连接操作。优点:只需连接一次就可close于数据库的连接。一般情况下,读取大量数据,对返回数据不做大量处理用SqlDataReader.对返回数据大量处理用datset比较合适.对SqlDataReader和Dataset的选择取决于程序功能的实现。
二、ExecuteNonQuery和ExecuteScalar 对数据的更新不需要返回结果集,建议使用ExecuteNonQuery。由于不返回结果集可省掉网络数据传输。它仅仅返回受影响的行数。如果只需更新数据用ExecuteNonQuery性能的开销比较小。
ExecuteScalar它只返回结果集中第一行的第一列。使用ExecuteScalar方法从数据库中检索单个值(例如id号)。与使用ExecuteReader方法,返回的数据执行生成单个值所需的操作相比,此操作需要的代码较少。
只需更新数据用ExecuteNonQuery.单个值的查询使用ExecuteScalar。数据绑定的选择
三、数据的绑定DataBinder 一般的绑定方法<%# DataBinder.Eval(Container.DataItem, “字段名”)%>用DataBinder.eval绑定不必关心数据来源(Dataread或dataset)。不必关心数据的类型eval会把这个数据对象转换为一个字符串。在底层绑定做了很多工作,使用了反射性能。正因为使用方便了,但却影响了数据性能。来看下<%# DataBinder.Eval(Container.DataItem, “字段名”)%>。当于dataset绑定时,DataItem其实式一个DataRowView(如果绑定的是一个数据读取器(dataread)它就是一个IdataRecord。)因此直接转换成DataRowView的话,将会给性能带来很大提升。.<%# ctype(Container.DataItem,DataRowView).Row(“字段名”)%> 对数据的绑定建议使用<%# ctype(Container.DataItem,DataRowView).Row(“字段名”)%>。数据量大的时候可提高几百倍的速度。使用时注意2方面:1.需在页面添加<%@ Import namespace=“System.Data”%>.2.注意字段名的大小写(要特别注意)。如果和查询的不一致,在某些情况下会导致比<%# DataBinder.Eval(Container.DataItem, “字段名”)%>还要慢。如果想进一步提高速度,可采用<%# ctype(Container.DataItem,DataRowView).Row(0)%>的方法。不过其可读性不高。以上的是vb.net的写法。在c#中:<@%((DataRowView)Container.DataItem)[“字段名”] %>
一、应用Ado.net的一些思考原则 1.根据数据使用的方式来设计数据访问层 2.缓存数据,避免不必要的操作 3.使用服务帐户进行连接 4.必要时申请,尽早释放 5.关闭可关闭的资源 6.减少往返
7.仅返回需要的数据 8.选择适当的事务类型 9.使用存储过程
二、Connection
数据库连接是一种共享资源,并且打开和关闭的开销较大。Ado.net默认启用了连接池机制,关闭连接不会真的关闭物理连接,而只是把连接放回到连接池中。因为池中共享的连接资源始终是有限的,如果在使用连接后不尽快关闭连接,那么就有可能导致申请连接的线程被阻塞住,影响整个系统的性能表现。
1、在方法中打开和关闭连接 这个原则有几层含义:
1)主要目的是为了做到必要时申请和尽早释放
2)不要在类的构造函数中打开连接、在析构函数中释放连接。因为这将依赖于垃圾回收,而垃圾回收只受内存影响,回收时机不定
3)不要在方法之间传递连接,这往往导致连接保持打开的时间过长 这里强调一下在方法之间传递连接的危害:曾经在压力测试中遇到过一个测试案例,当增大用户数的时候,这个案例要比别 的案例早很久就用掉连接池中的所有连接。经分析,就是因为A方法把一个打开的连接传递到了B方法,而B方法又调用了一个
自行打开和关闭连接的C方法。在A方法的整个运行期间,它至少需要占用两条连接才能够成功工作,并且其中的一条连接占用时间还特别长,所以造成连接池资源紧张,影响了整个系统的可伸缩性!
2、显式关闭连接
Connection对象本身在垃圾回收时可以被关闭,而依赖垃圾回收是很不好的策略。推荐使用using语句显式关闭连接,如下例:
using(SqlConnection conn = newSqlConnection(connString)){ conn.Open();} // Dispose is automatically called on the conn variable here
3、确保连接池启用
Ado.net是为每个不同的连接串建立连接池,因此应该确保连接串不会出现与具体用户相关的信息。另外,要注意连接串是 大小写敏感的。
4、不要缓存连接
例如,把连接缓存到Session或Application中。在启用连接池的情况下,这种做法没有任何意义。
三、Command
1、使用ExecuteScalar和ExecuteNonQuery 如果想返回像Count(*)、Sum(Price)或Avg(Quantity)那样的单值,可以使用ExecuteScalar方法。ExecuteScalar返回第一行第一列的值,将结果集作为标量值返回。因为单独一步就能完成,所以ExecuteScalar不仅简化了代码,还提高了性能。
使用不返回行的SQL语句时,例如修改数据(INSERT、UPDATE或DELETE)或仅返回输出参数或返回值,请使用ExecuteNonQuery。这避免了用于创建空DataReader的任何不必要处理。
2、使用Prepare 当需要重复执行同一SQL语句多次,可考虑使用Prepare方法提升效率。需要注意的是,如果只是执行一次或两次,则完全没有必要。例如: cmd.CommandText = “insert into Table1(Col1, Col2)values(@val1, @val2)”;cmd.Parameters.Add(“@val1”, SqlDbType.Int, 4, “Col1”);cms.Parameters.Add(“@val2”, SqlDbType.NChar, 50, “Col2”);cmd.Parameters[0].Value = 1;cmd.Parameters[1].Value = “XXX”;cmd.Prepare();cmd.ExecuteNonQuery();cmd.Parameters[0].Value = 2;cmd.Parameters[1].Value = “YYY”;cmd.ExecuteNonQuery();cmd.Parameters[0].Value = 3;cmd.Parameters[1].Value = “ZZZ”;cmd.ExecuteNonQuery();
3、使用绑定变量
SQL语句需要先被编译成执行计划,然后再执行。如果使用绑定变量的方式,那么这个执行计划就可以被后续执行的SQL语句所复用。而如果直接把参数合并到了SQL语句中,由于参数值千变万化,执行计划就难以被复用了。例如上面Prepare一节给出的示例,如果把参数值直接写到insert语句中,那么上面的四次调用将需要编译四次执行计划。
为避免这种情况造成性能损失,要求一律使用绑定变量方式。
四、DataReader DataReader最适合于访问只读的单向数据集。与DataSet不同,数据集并不全部在内存中,而是随不断发出的read请求,一旦发现数据缓冲区中的数据均被读取,则从数据源传输一个数据缓冲区大小的数据块过来。另外,DataReader保持连接,DataSet则与连接断开。
1、显式关闭DataReader 与连接类似,也需要显式关闭DataReader。另外,如果与DataReader关联的Connection仅为DataReader服务的话,可考虑使用Command对象的ExecuteReader(CommandBehavior.CloseConnection)方式。这可以保证当DataReader关闭时,同时自动关闭Connection。
2、用索引号访问代替名称索引号访问属性
从Row中访问某列属性,使用索引号的方式比使用名称方式有细微提高。如果会被频繁调用,例如在循环中,那么可考虑此类优化。示例如下:
cmd.CommandText = “select Col1, Col2 from Table1”;SqlDataReaderdr = cmd.ExecuteReader();int col1 = dr.GetOrdinal(“Col1”);int col2 = dr.GetOrdinal(“Col2”);while(dr.Read()){ Console.WriteLine(dr[col1] + “_” + dr[col2]);}
3、使用类型化方法访问属性
从Row中访问某列属性,用GetString、GetInt32这种显式指明类型的方法,其效率较通用的GetValue方法有细微提高,因为不需要做类型转换。
4、使用多数据集
部分场景可以考虑一次返回多数据集来降低网络交互次数,提升效率。示例如下:
cmd.CommandText = “StoredProcedureName”;// The stored procedure returns multiple result sets.SqlDataReaderdr = cmd.ExecuteReader();while(dr.read())// read first result setdr.NextResult();while(dr.read())
五、DataSet
1、利用索引加快查找行的效率
如果需要反复查找行,建议增加索引。有两种方式: 1)设置DataTable的PrimaryKey 适用于按PrimaryKey查找行的情况。注意此时应调用DataTable.Rows.Find方法,一般惯用的Select方法不能利用索引。2)使用DataView 适用于按Non-PrimaryKey查找行的情况。可为DataTable创建一个DataView,并通过SortOrder参数指示建立索引。此后使用Find或FindRows查找行。
一、减少往返行程(Reduce Round Trips)
使用下面的方法可以减少Web服务器和Browser之间的往返行程:
1、为Browser启用缓存
如果呈现的内容是静态的或变化周期较长,应启用Browser缓存,避免发出冗余的http请求。
2、缓冲页面输出
如果可能,则尽量缓冲页面输出,处理结束后再一次传送到客户端,这可以避免频繁传递小块内容所造成的多次网络交互。由于这种方式在页面处理结束之前客户端无法看到页面内容,因此如果一个页面的尺寸较大的话,可考虑使用Response.Flush方法。该方法强制输出迄今为止在缓冲区中的内容,你应当采用合理的算法控制调用Response.Flush方法的次数。
3、使用Server.Transfer重定向请求
使用Server.Transfer方法重定向请求优于Response.Redirect方法。原因是Response.Redirect会向Broswer回送一个响应头,在响应头中指出重定向的URL,之后Brower使用新的URL重新发出请求。而Server.Transfer方法直接是一个简单的服务端调用,完全没有这些开销!需要注意Server.Transfer有局限性:第一,它会跳过安全检查;第二,只适用于在同一Web应用内的页面间跳转。
二、避免阻塞和长时间的作业
如果需要运行阻塞或长时间运行的操作,可以考虑使用异步调用的机制,以便Web服务器能够继续处理其它的请求。
1、使用异步方式调用Web服务和远程对象
只要有可能就要避免在请求的处理过程中对Web服务和远程对象的同步调用,因为它占用的是的ASP.NET 线程池中的工作线程,这将直接影响Web服务器响应其它请求的能力。
2、考虑给不需要返回值的Web方法或远程对象的方法添加OneWay属性
这种模式能让Web Server调用之后就立即返回。可根据实际情况决定是否使用这种方法。
3、使用工作队列
将作业提交到服务器上的工作队列中。客户端通过发送请求来轮询作业的执行结果。
三、使用缓存
缓存能在很大程度上决定ASP.NET应用的最终性能。Asp.net支持页面输出缓存和页面部分缓存,并提供Cache API,供应用程序缓存自己的数据。是否使用缓存可考虑下面的要点:
1、识别创建与访问代价较大的数据
2、评估需要缓存数据的易变性
3、评估数据的使用频次
4、将要缓存数据中易变数据和不变数据分离,只缓存不变数据
5、选择合适的缓存机制(除Asp.net Cache外,Application state和Session state也可以作为缓存使用)
四、多线程
1、避免在请求处理过程中创建线程
在执行请求的过程中创建线程是一种代价较大的操作,会严重影响Web Server的性能。如果后续的操作必须用线程完成,建议通过thread pool来创建/管理线程。
2、不要依赖线程数据槽或线程静态变量
由于执行请求的线程是ASP.NET thread pool中的工作线程,同一个Client的两次请求不一定由相同的线程来处理。
3、避免阻塞处理请求的线程
4、避免异步调用
这和1的情况类似。异步调用会导致创建新的线程,增加服务器的负担。所以,如果没有并发的作业要执行,就不要执行异步调用。
五、系统资源
1、考虑实现资源池以提升性能
2、明确地调用Dispose或Close释放系统资源
3、不要缓存或长时间占用资源池中的资源
4、尽可能晚的申请,尽可能早的释放
六、页面处理
1、尽量减小Page的尺寸
包括缩短控件的名称、CSS的class的名称、去掉无谓空行和空格、禁用不需要的ViewState
2、启用页面输出的缓冲区(Buffer)
如果Buffer的机制被关闭,可以用下面的方法打开。使用程序打开页面输出缓存: Response.BufferOutput = true;使用@Page开关打开页面输出缓冲机制: <%@ Page Buffer = “true” %> 使用Web.config或Machine.config配置文件的
节点:
3、利用Page.IsPostBack优化页面输出
4、通过分离页面的不同的内容,来提高缓存效率和减少呈现的时间
5、优化复杂和代价较大的循环
6、合理利用客户端的计算资源,将一些操作转移到客户端进行
七、ViewState ViewState是Asp.net为服务端控件在页面回传之间跟踪状态信息而设计的一种机制。1.关闭ViewState 如果不需要跟踪页面状态,例如页面不会回传(PostBack)、不需要处理服务端控件事件或者每次页面刷新时都会重新计算控件内容,那么就不需要用ViewState来记录页面状态了。可以对特定的WebControl设置EnableViewState属性,也可以在页面一级设置: <%@ Page EnableViewState=“false” %>
2、在恰当的时间点初始化控件属性
ASP.NET的控件在执行构造函数、初始化的期间设置的属性不会被跟踪变化;而在初始化阶段之后对属性的修改都会被跟踪,并最终记录到IE页面的__VIEWSTATE之中。所以,选择合理的初始化控件属性的执行点,能有效的减小页面尺寸。
3、谨慎选择放到ViewState中的内容
放到ViewState中的内容会被序列化/反序列化,Asp.net为String、Integer、Boolean等基本类型的序列化做了优化,如果Array、ArrayList、HashTable存储的是基本类型效率也较高,但其它类型则需要提供类型转换器(Type Converter),否则将使用代价昂贵的二进制序列化程序。
第三篇:计算机系统性能优化总结
计算机系统性能优化总结
现今,计算机技术在社会各行各业都得到了广泛的应用。计算机给我们的学习、生活和工作都带来了极大的便利。但随着我们对计算机整体性能要求的提高,计算机系统性能的优化就显得尤为重要。
一、计算机系统运行不佳的原因分析
计算机系统运行性能不佳的原因有很多。如,系统平台结构不好、系统配置不好或参数设置不对;应用系统数据结构设计不合理,加大了系统的输入和输出需求;应用系统算法或逻辑处理有问题,使计算机系统达不到最佳的运行状态。
二、计算机系统性能优化措施
1.合理地配置各种软件,使计算机系统发挥最好的功能。计算机系统由硬件系统和软件系统组成,二者之间相互依赖,这就要求我们在使用计算机软件的过程中,使用一些速度较快、版本较高和功能较完善的软件,并仔细阅读各种软件的使用说明,避免在应用过程中发生冲突。作为编程人员,在编写应用程序的过程中,要充分考虑应用系统数据结构设计的合理性,以便使计算机系统达到最佳的运行状态。
2.调整输入和输出系统。在计算机系统的应用过程中,我们进行的大多数操作就是输入和输出。因此,输入和输出操作是影响计算机性能的一个重要因素。随着科技的日益发
展,磁盘的平均寻址时间日益缩短,但与中央处理器的运算相比,仍然缓慢很多。在观察一些系统运行时,经常出现中央处理器处在空闲状态而应用程序却迟迟不能完成的情况。究其原因,就是因为磁盘的输入和输出的速度太慢,数据没有读(写)入内存中。因此,在实际的应用过程中,我们可以考虑把数据文件存放在不同的磁盘上,让多个磁盘并行工作,从而解决输入和输出的瓶颈问题。如果输入和输出总数明显不合理,就要考虑查找引起输入和输出数量增大的原因,从而优化应用程序,减少输入和输出的次数,提高系统的性能。
3.安排相同性质的处理过程同时运行,以确保中央处理器和输入和输出的绝对通畅。一台计算机能够同时运行多个应用程序,从使用系统资源的角度来看,这些应用程序可以分为面向输入和输出与面向运算2种类型。
系统中如果有2个或多个面向输入和输出的应用在同时运行,就会造成中央处理器闲置而大量磁盘输入和输出拥塞和等待的情况,使得各个应用程序的性能变差。系统中如果有2个或多个面向运算的应用程序同时运行时,就会造成磁盘空转的情况。因此,要尽量避免让多个面向输入和输出或多个面向运算的应用程序同时运行。最好的安排就是让面向输入和输出与面向运算的应用程序合理搭配,使每个应用都能获得足够的系统服务而又互不影响。
4.合理地使用中央处理器。一般来说,在一个计算机系统中,中央处理器的速度要远远高于输入和输出的速度,因而输入和输出速度往往是影响系统性能的主要因素。但必须指出的是,这种规则只适用于普通的情况。如果不知道中央处理器能力也有一定限制,盲目地、不合理地使用中央处理器,中央处理器也会成为影响系统性能的主要因素。
通过对计算机系统的性能进行优化,排除了系统中的各种不合理因素,缩短了系统的响应时间,使计算机系统能更好地发挥作用,从而为我们提供更好的服务。
第四篇:网站前端性能优化总结
一、服务器侧优化
1.添加 Expires 或 Cache-Control 信息头
某些经常使用到、并且不会经常做改动的图片(banner、logo等等)、静态文件(登录首页、说明文档等)可以设置较长的有效期(expiration date),这些HTTP头向客户端表明了文档的有效性和持久性。如果有缓存,文档就可以从缓存(除已经过期)而不是从服务器读取。接着,客户端考察缓存中的副本,看看是否过期或者失效,以决定是否必须从服务器获得更新。
各个容器都有针对的方案,,以 Apache 为例:
ExpiresActive On ExpiresByType image/gif “access plus 1 weeks”
表示gif文件缓存一周,配置可以根据具体的业务进行调整,具体配置可以参考:http://lamp.linux.gov.cn/Apache/ApacheMenu/mod/mod_expires.html
2.压缩内容
对于绝大多数站点,这都是必要的一步,能有效减轻网络流量压力。
表示zlib在压缩时可以最大程度的使用内存,压缩html、文本、xml和php这几种类型的文件,指定扩展名为html、htm、xml、php、css和js的文件启用压缩。
具体配置可以参考:http://lamp.linux.gov.cn/Apache/ApacheMenu/mod/mod_deflate.html
3.设置 Etags
在使用etags之前,有必要复习一下 RFC2068 中规定的返回值 200 和 304 的含义:
200--OK 304--Not Modified
客户端在请求一份文件的时候,服务端会检查客户端是否存在该文件,如果客户端不存在该文件,则下载该文件并返回200;如果客户端存在该文件并且该文件在规定期限内没有被修改(Inode,MTime和Size),则服务端只返回一个304,并不返回资源内容,客户端将会使用之前的缓存文件。而etags就是判断该文件是否被修改的记号,与服务器端的资源一一关联,所以etags对于CGI类型的页面缓存尤其有用。
下图是优化前的首页:(注意,此时没有压缩首页图片,即使使用了缓存,仍需要5s左右的时间)
化前的某页面
需要注意的是,使用etags会增加服务器端的负载,在实际应用中需要自行平衡。
二、Cookie优化
1.减小Cookie体积
HTTP coockie可以用于权限验证和个性化身份等多种用途。coockie内的有关信息是通过HTTP文件头来在web服务器和浏览器之间进行交流的。因此保持coockie尽可能的小以减少用户的响应时间十分重要。
使cookie体积尽量小;
在合适的子域名上设置bookie,以免影响其他子域名下的响应;
设置合理的过期时间,去掉不必要的cookie。
下面对比一下各个网站的cookie:
图中可以看出,6K的cookie显然是不必要的。
2.对于页面内容使用无coockie域名
当浏览器在请求中同时请求一张静态的图片和发送coockie时,服务器对于这些coockie不会做任何地使用。因此它们只是因为某些负面因素而创建的网络传输。所以你应该确定对于静态内容的请求是无coockie的请求。创建一个子域名并用他来存放所有静态内容。
例如,域名是
3.切分组件到多个域
主要的目的是提高页面组件并行下载能力,但注意,也不要同时使用过多的域名,否则就会出现第一条DNS lookup过多的问题,一般情况下两个域名就可以了。
4.杜绝 http 404 错误
对页面链接的充分测试加上对 Web 服务器 error 日志的不断跟踪可以有效减少 404 错误,并提升用户体验。
后记:
这次总结给我带来的启发并不在于提升系统性能性能本身,提升性能只是一个很表面上的东西,网上的方法有很多,测试的方法也有很多,照着都做一遍,性能确实会有所提升,但是这种知其然而不知其所以然的性能提升是没有意义的,这便是本文的目的所在。
第五篇:SQL语句性能优化
我也做了很长时间医疗软件,也写过不少sql优化,没有详细记录下来,个人感觉下面转载的更符合医院医疗软件实际业务,很认可大部分所写的原则,固转载过来,以作借鉴。软件的根本还是在于更细更精,在于从客户的实际使用考虑问题。
性能优化原则1:永远避免困境
利用缓存把字典数据取到中间服务器或是客户端替代直接sql查询,如,门诊医生站把字典下载到客户端,减少执行次数。
一次性取数据到客户端,然后再逐条处理,而不是分次取数据,处理好一条数据再取下一条再处理。例:门诊收费取hjcfmxk例子,原来是一张处方条明细都查询一次,查询后再处理,现改为一次把所有明细都取过来,然后一条条处理
尽量减少光标,看能不能用临时表
性能优化原则2:kiss原则
对于where 条件中的左边可以利用索引的字段Keep it simple stupid,左边尽量避免用函数(substring,isnull,upper,lower),参加计算+,-*/
例子1:select * from ZY_BRFYMXK where substring(zxrq,1,8)='20081212‘
select * from ZY_BRFYMXK where zxrq between '2008121200' and '2008121224' 例子2:
select * from zy_detail_charge where SUBSTRING(patient_id,1,10)=
substring('000005090600',1,10)这句耗时30秒以上
select * from zy_detail_charge where patient_id like substring('000005090600',1,10)+'%' 这句耗时2秒以内
性能优化原则3:尽可能利用到索引
例:select * from ZY_BRFYMXK a(nolock),VW_LSYZK b(nolock)where a.syxh=3 and a.yzxh=b.xh and a.fylb=0
select * from ZY_BRFYMXK a(nolock),VW_LSYZK b(nolock)where a.syxh=3 and a.yzxh=b.xh and a.fylb=0 and b.syxh=3
性能优化原则4:or,避而远之
对于索引字段尽力避免用or,普通字段可以用or,解决要么分解成多个sql,要么用业务规则避免,例:declare @rq1 ut_rq16,@syxh ut_syxh
select @rq1='20081201'
select @syxh=157
性能优化原则5:避免大批量数据取到前台
例: select * from ZY_BRSYK cyrq between ‘20080901’ and ‘20081201‘,对于大医院每天100多人,90天是9000条数据
性能优化原则6:事务,尽可能的短吧
所有计算、对临时表的更新都应但放在事务外,事务中最好只有更新和插入正式表操作.因为事务中产生的锁只有在commit tran是才会释放。
性能优化原则7:热表,留在最后吧
热表是频繁调用的表。如:sf_mzcfk,zy_brfymxk,bq_fyqqk.对于热表尽量放在事务最后:这样锁的时间短。大家都坚持这样,死锁的可能性就小。如果都是热表各个存储过程更新表的顺序应当一样这样可以避免死锁
性能优化原则8:创建临时表一定要避免在事务中作
如create #tempXX(…)
Select * into #tempXX from …
因为创建临时表会锁tempdb的系统表
例:生成#temp1放在事务内外,用sp_lock2 ‘’观察结果
if object_id('tempdb..#temp1','U')is not null
drop table #temp1
begin tran
select * into #temp1 from ZY_BRSYK where ryrq>'20080901‘
select * from #temp1
waitfor delay '00:00:10'
commit
性能优化原则9:大的报表查询避免与正常业务碰撞
如果没有查询服务器,那要在存储过程中限制不能操作加上如:
declare @rq1 ut_rq16,@rq2 ut_rq16,@now ut_rq16
select @rq1=convert(varchar(8),getdate(),112)+'08:00:00'
select @rq1=convert(varchar(8),getdate(),112)+'11:30:00'
select @now=convert(char(8),getdate(),112)+convert(char(8),getdate(),8)
if @now>@rq1 and @now<@rq2
begin
select '上午繁忙时间段不能作此查询'
return
end
性能优化原则10:存储过程避免大的if…else…
这个常出项在业务相同表不同的存储过程中,因为这样常到if …else …原来医技接口中很多这种存储过程,当时把门诊住院业务放在一个存储过程中。这样最大的问题是sql server会根据sql语句来compile存储,这个过程会生成优化计划,决定用那个索引。如果存储过程用到门诊表compile一下,到用到住院表是发现不对,又会compile一下,这样不停compile.compile很号时间要1-2秒,而且一个存储过成在compile是,所有调用这个存储过程的进程都要在排队等候,因为他会独占锁这个存储过程
例:usp_yjjk_getwzxxm_old.sql,后改为:
usp_yjjk_getwzxxm.sql, usp_yjjk_getwzxxm_mz.sql,usp_yjjk_getwzxxm_zy.sql
性能优化原则11:进攻是最好的防守
在普通编程语句对于数据校验总是用防守办法先判断,后再作相应处理。而在sql中先处理再判断性能会好很多。
--更新药品库存。
If exists(select 1 from YK_YKZKC WHERE idm=100 and kcsl>50)
begin
update YK_YKZKC set kcsl=kcsl-50 where idm=100
End
Else begin
rollback tran
Select ‘F库存不够’
return
end
--改为
update YK_YKZKC set kcsl=kcsl-50 where idm=100 and kcsl>50
If @@rowcount<=0
Begin
Rollbakc tran
Select ‘F库存不够’
end
--取未执行的医技项目,日表没有数据就到年表中查找
if exists(select a.* from SF_MZCFK a(nolock),SF_CFMXK b(nolock)
begin
select a.* into #temp1 from SF_MZCFK a(nolock),SF_CFMXK b(nolock)
end
else begin
select a.* into #temp1 from SF_NMZCFK a(nolock),SF_NCFMXK b(nolock)
end
--改为
Insert into #temp1 select a.*
from SF_MZCFK a(nolock),SF_CFMXK b(nolock)
If @@rowcount=0
Begin
Insert into #temp1 select a.*
from SF_NMZCFK a(nolock),SF_NCFMXK b(nolock)
end
性能优化原则12:trig最后的手段
Trig(触发器)的处理的处理机制是满足条件时就会在源语句后面加上trig中的代码进行执行。
它有两个致命的弊端:(1)不清楚有trig的人会对于执行结果感到迷惑。如常有在插入一张表如果主键是indentity的值常取用select @@identity。但如是有trig,tring中有表插入操作,这时的@@identity可能就不是想要的值。(2)trig会束缚选择。如:有一套单据主表和明细表,当明细表的金额更新时,要同步主表的金额,当程序是一条条更新明细时用trig的作法是每当更新一条明细记录时都算一处所有明细表的总金额,再去更新主表的金额。这样有多少条明细就要算多少次,好的作法是不要trig,直接在sql语句中明细更新完明后,一次性算出总金额每条单据的总金额,再更新主表的金额。
对于trig如果有其他手段就一定要避免用trig.性能优化原则13:用户说好才是真的好
1)有时sql语句性能难以优化,但用户对于系统响应速度还是不满意。这时可以从业务分析处理。
如:我们退费模块录入发票号原来是用fph like ‘XXX%’。用户报怨慢,后来改为先用fph=‘XXX’来查,如查不到再fph like ‘XXX%’。这样在绝大部情况下速度都非常快,同时也满足小部分情况下模糊查询的需求。
如:我们的程序要查日表和年表。如果通过日表union表视图去查会非常慢,性能也难以优化。程序改为普通情况下不查年表,用户勾上年表标志时才查年表。
(2)查询统计很多数据时间比较长,就以查询完一部分数据后可以显示这部分数据或是用提示,这样用户清楚系统在作事情也知道大概进度。这样情绪上会好很多。
(3)查询模块常有一进入时也默认一个查询,如果性能好,查询又合用户心意,这种设计非常好,如果性能不好,那就不是好的设计。用户对于进入都困难的模块是没有好感的。
(4)有户的耐心与查询出的记录成正比。用户痛恨等待很久却没有查询出记录。
对于非常慢的查询,如果有些子查询非常快可以先作这样查询以避免查询很久却没有数据出来的情况。如:按病历号查在院病人所有费有明细,可以先查一下这个病历是不是有对应病人。
实战技巧1:用exists、in代替distinct
Distinct实际上是先收集再删除这样两步都耗资源。
Exists,in会隐式过滤掉重复的记录
例查自2009年以来有金额大于100的药品的病人
select distinct a.blh,a.hzxm from ZY_BRXXK a(nolock),ZY_BRSYK b(nolock),ZY_BRFYMXK c(nolock)where a.patid=b.patid and b.syxh=c.syxh and c.zxrq>'2009' and c.zje>100--改为
select a.blh,a.hzxm from ZY_BRXXK a where exists(select 1 from ZY_BRSYK
b(nolock),ZY_BRFYMXK c(nolock)where a.patid=b.patid and b.syxh=c.syxh and
c.zxrq>'2009'and c.zje>100)
实战技巧2:缩短union
select …from A,B,C,D,E1
where(E1的条件)
and(其他表联接条件)
union
select …from A,B,C,D,E2
where(E2的条件)
and(其他表接接条件)
改为
select …from A,B,C,D,(select...from E1where(E1条件)
union
select …from E2where(E2条件))E where(其他条件)
当涉及ABCD表部分耗资源而E1,E2不耗资源时是这样,如果反过来则改后的性能不一定好。查2009年4月后入院的在院病人在2905病区发生的所有费用明细
select a.hzxm,b.cyrq,d.ypmc,d.ypgg,c.ypsl/c.dwxs ypsl, c.ypdw
select a.hzxm,b.cyrq,d.ypmc,d.ypgg,c.ypsl/c.dwxs ypsl, c.ypdw
from ZY_BRXXK a(nolock),ZY_BRSYK b(nolock),ZY_BRFYMXK c(nolock),YK_YPCDMLK d where a.patid=b.patid and b.ryrq>'200904' and b.brzt not in(3,8,9)and b.syxh=c.syxh and c.bqdm='2905' and c.idm=d.idm
union all
select a.hzxm,b.cyrq,d.name,d.xmgg,c.ypsl/c.dwxs ypsl, c.ypdw
from ZY_BRXXK a(nolock),ZY_BRSYK b(nolock),ZY_BRFYMXK c(nolock),YY_SFXXMK d where a.patid=b.patid and b.ryrq>'200904' and b.brzt not in(3,8,9)and b.syxh=c.syxh and c.bqdm='2905' and c.ypdm=d.id and c.idm=0
--改为
select a.hzxm,b.cyrq,d.ypmc,d.ypgg,c.ypsl/c.dwxs ypsl, c.ypdw
from ZY_BRXXK a(nolock),ZY_BRSYK b(nolock),ZY_BRFYMXK c(nolock),(select ypmc,ypgg,ypdm,idm idm from YK_YPCDMLK union select name,xmgg,id,0 from YY_SFXXMK)d
where a.patid=b.patid and b.ryrq>'200904' and b.brzt not in(3,8,9)and b.syxh=c.syxh and c.bqdm='2905' and c.idm=d.idm and c.ypdm=d.ypdm
实战技巧3:合并sql
把表和where条件类似的两个或是多个sql合并为一个sql.--查2009年以后的普通、急诊、专家挂号人数
declare @ptghs int,@jzghs int,@zjghs int
select @ptghs=0,@jzghs=0,@zjghs=0
select @ptghs=count(*)from GH_GHZDK where ghrq>'2009' and ghlb=0
select @jzghs=count(*)from GH_GHZDK where ghrq>'2009' and ghlb=1
select @zjghs=count(*)from GH_GHZDK where ghrq>'2009' and ghlb=2
select @ptghs,@jzghs,@zjghs
--改为
select @ptghs=0,@jzghs=0,@zjghs=0
select @ptghs=sum(case when ghlb=0 then 1 else 0 end),@jzghs=sum(case when ghlb=1 then 1 else 0 end), @zjghs=sum(case when ghlb=2 then 1 else 0 end)
from GH_GHZDK where ghrq>'2009'
select @ptghs,@jzghs,@zjghs
实战技巧4:去掉游标
把游标当作编程语言的for,do---while的方式,很多情况下都可以去掉,如果光标中间sql语句只有一条一般都是可以去掉光标改为一句sql。
--查当天出院出院日期在2009年4月1到9日间病人的zfdj,zfje置为0
declare @syxh ut_syxh
declare cur1 cursor for select syxh from ZY_BRSYK where cyrq>='20090401' and cyrq<'20090410'
open cur1
fetch cur1 into @syxh
while @@fetch_status=0
begin
fetch cur1 into @syxh
end
close cur1
deallocate cur1
--改为
update ZY_BRFYMXK set zfdj=0,zfje=0
from ZY_BRFYMXK a,ZY_BRSYK b
where a.syxh=b.syxh and b.cyrq>='20090401' and b.cyrq<'20090410'
实战技巧5:取代count
利用内部函数代替
declare @count int
select * into #tmep1 from ZY_BRFYMXK WHERE zxrq>'200901'
select @count=@@rowcount—可以得到count值
select @count
select @count=count(*)from #tmep1—可以被取代
select @count
利用exists而不count判断有没有记录
declare @count int
Select @count=count(1)from ZY_BRFYMXK WHERE zxrq>'2009‘
If @count>0 … else ….--改为
If exists(Select 1 from ZY_BRFYMXK WHERE zxrq>'2009’)… else ….