用.Net写的网站运行一段时间后为啥性能越来越差?

码农公社  210.net.cn   210是何含义?10月24日是程序员节,1024 = 210、210既 210 之意。

老板或客户是否和你抱怨过:网站性能咋愈来愈差!

本文提供一些改善网站性能的办法,硬件、软件、程序技巧的层面都有,也欢迎大家分享自己的经验或秘技。


(1) 重新调整或重新设计 DB schema、索引 (index)  

     一个系统的性能不佳,主因是来自于数据库规划及 SQL 语句书写层面, .NET 程序编写不良都还在其次。  先将数据库适度地做优化,如:一个 Table 中,避免把高频字段、低频字段,都塞在同一个表中,否则影响数据扫描的速度。 应该将很少用的低频字段,另拿出来组成一个表。


(2) 改写 SQL 语句,注意 index 是否在查询时被用到  

     同样的功能,一个不良的「关联子查询」和良好的「独立子查询」之间的性能差距是很明显的。  子查询分「关联子查询 (correlated subquery)」、「独立子查询」,前者应尽可能少用。 以下两个SQL 语句功能相同,都是要找出每位客户最近一笔订单的日期:

         无标题.jpg

    前者每次处理一笔记录时,都会执行一次内部查询 (关联子查询),有点类似双层循环 (loop),会导致性能大幅降低;有经验的 SQL 程序员,会改用后者 JOIN + GROUP BY 的做法,因为这样做,不会执行任何内部查询,会比前者更有效率。  

    当两个表的记录笔数越多时,在较极端的情况下,性能的差距,就是一秒和十几分钟的量级。   

    一些 SQL 关键词,只要一出现在 SQL 语句中,就可能造成表的「索引 (index)」完全失效或部分失效,变成要整个表去逐行逐列地扫描, 例如: NOT、NOT IN、!=、<>、OR ...等关键词, 还有「LIKE '%关键词'」的模糊查询,也会造成索引失效,但「LIKE '关键词%'」就不会造成索引失效。


(3) 使用 Native 的 DataProvider  

     放弃 OleDb,改用 ADO.NET Native 的 DataProvider,如: SqlClient、OracleClient。但您公司若坚持要用 Sybase 这种从 2003 年之后,就不曾更新 DB driver 的数据库,就只好继续用性能不佳的 OleDb 去联机了。  据我用 Visual Studio 内建的 stress test 工具,测试 OleDb 和 SqlClient 的性能差距,模拟 30 人同时上线,用浏览器撷取一万笔数据,两者的速度就差了一秒钟;且当数据库的数据越多,或越多人同时上线时,性能差距会更明显。


(4) 用程序或软件做缓存  

     用程序做缓存,如 ASP.NET 从 1.x 时代,就已内建的 Cache (缓存) 机制;或用一些第三方的辅助软件、Framework,这方面若有其它网友知道好用的软件,亦恳请留言告知。


(5) 用硬件做快取或缓冲、砸钱加装 AP Server


(6) 加装实体机器做 Loading Balance (负载平衡)。一些 Server OS 亦内建此类设定功能。


(7) 程序技巧 - ADO.NET  

     能用 DataReader 就不要用 DataSet / DataTable,前者读取速度快又不耗内存;后者虽较有弹性,但速度较慢又会消耗许多内存。若您连 DropDownList 控件的数据来源,都用 SqlDataSource 控件的默认值 - DataSet,则当页面里塞了一堆下拉选单时,性能当然会受影响。

     但前提是程序员对 ADO.NET 要有一定程度的了解,若只会用 Visual Studio 透过图形界面,拖拉 TableAdapter、DataTable、.xsd 就免谈了。

     若为 DataTable 建立 Primary Key,DataTable 会建立一个索引,追踪新增到 DataTable 中的数据是否符合此条件约束 (constraint)。ADO.NET 2.0 会使用 algorithm 的「红黑树算法 (Red-Black Tree,是一种「平衡树」算法) 去处理索引,让 DataTable 的数据量大时,较方便维护索引;但缺点是建立索引时会降低一些性能。

     此外,数据库的访问和取值,应该尽量在一次 DB connection 做完,一个 connection 可搭配多个 DbCommand 对象使用,不用每次都一个 DbConnection 配一个 DbCommand


(8) 程序技巧 - .NET 语法  

     避免把 DataTable 或大量数据直接塞进 Session 里。Session 在多人同时上线时,内存的消耗是很大的,因为 Session 是每个用户各存一份在服务器的内存里,而非像「缓存 (cache)」是所有的用户共享服务器的一块内存。在很多 ASP.NET 的需求中,可用 HiddenField 控件或  ViewState 取代 Session。  

     能用「泛型 (Generics)」就不要用旧版的写法,Generics 除了安全外,亦可避免 .NET 类型在 Boxing / Unboxing 转型时影响性能,例如:  能用 List<string> 就不要用旧的 ArrayList,能用 Dictionary<TKey,TValue> 就不要用 Hashtable 或跑双层的循环 (loop),因 ArrayList、Hashtable 的 element 属于 object 类,在存储或检索如 int 等「值类型 (Value Type)」时,会引发 Boxing / Unboxing。 

     在大多数的情况下,List、Dictionary 等泛型类,拥有较佳的效率,而且是类型安全的。  

  

(9) 程序技巧 - 数据库「事务 (Transaction)」  

     您是否知道 SQL Server 的默认「事务隔离等级 (Isolation Level)」,是「ReadCommitted」,当您在写 ADO.NET 程序时用了 SqlTransaction ,默认是当某个人在修改某一笔记录时,其它所有读取这一笔记录的人,都会被「锁定 (lock)」住,造成其它全部用户的浏览器都在等待中,无法做其它工作。  

     而 Oracle 事务的特性,是绝不会有类似无法读取的情形,至少会用类似 SQL Server 2005 新增的「快照隔离 (Snapshot Isolation)」,让用户至少能先读取到未 Commit 或 Rollback 的记录,而不用呆坐在浏览器前面傻等。  

     不过 SQL Server 2005 的「快照隔离」默认未启用。用 SQL Server 开发的系统,若怕用户被锁定的问题,可视 project 需求,改用最宽松的「ReadUncommitted」事务隔离等级,其特性为不会造成任何锁定,但可能会造成 Dirty Read。SQL Server 有下列七种「事务隔离等级」,有兴趣的网友可去查询 ADO.NET 书籍或 MSDN Library:

     Chaos 

     ReadCommitted  // SQL Server 默认值 

     ReadUncommitted // 最宽松,会有 Dirty Read 

     RepeatableRead 

     Serializable    // 最严,会有大量的锁定

     Snapshot 

     Unspecified


(10) ASP.NET 分页  

       GridView + SqlDataSource 的默认行为,就是每次换页或排序时,不管数据库有几笔记录都全部读取一次;当数据库有一百万笔数据,就在每个用户换页时,都一百万笔全部重读出来,此举消耗了大量的 Web server/ AP server 内存、数据库系统资源、网络频宽,结果网站性能可想而知。  

       很多企业内的小型网站,为了省钱,随便外包给低价抢标的工作室,或没经验的学生和 SOHO 族,可能因此埋下了恐怖的后遗症。最可怕的是这些未爆弹,在开发期间和系统刚上线、数据量还很少时,都感觉不出来,有如癌症一样,会在将来忽然爆发。


(11) Design Patterns  

       虽然「设计模式」不是为解决性能问题而诞生的,但可适度防止没经验的新人做出蠢事。此外,多了解一些 .NET系统架构、OOAD、Design Patterns 和相关的 Framework,对提升开发者的身价和薪资也有帮助。


评论