一、返回多个数据集
检查你的访问数据库的代码,看是否存在着要返回多次的请求。每次往返降低了你的应用程序的每秒能够响应请求的次数。通过在单个数据库请求中返回多个结果集,可以减少与数据库通信的时间,使你的系统具有扩展性,也可以减少数据库服务器响应请求的工作量。
如果你是用动态的SQL语句来返回多个数据集,那我建议你用存储过程来替代动态的SQL语句。是否把业务逻辑写到存储过程中,这个有点争议。但是我认为,把业务逻辑写到存储过程里面可以限制返回结果集的大小,减小网络数据的流量,在逻辑层也不用在过滤数据,这是一个好事情。
用SqlCommand对象的ExecuteReader方法返回一个强类型的业务对象,再调用NextResult方法来移动数据集指针来定位数据集。示例一演示了一个返回多个ArrayList强类型对象的例子。只从数据库中返回你需要的数据可以大大的减小你的服务器所耗用的内存。
二、对数据进行分页
ASP.NET的DataGrid有一个非常有用的功能:分页。如果DataGrid允许分页,在某一时刻它只下载某一页的数据,另外,它有一个数据分页的浏览导航栏,它让你可以选择浏览某一页,而且每次只下载一页的数据。
但是它有一个小小的缺点,就是你必须把所有的数据都绑定到DataGrid中。也就是说,你的数据层必须返回所有的数据,然后DataGrid再根据当前页过滤出当前页所需要的数据显示出来。如果有一个一万条记录的结果集要用DataGrid进行分页,假设DataGrid每页只显示25条数据,那就意味着每次请求都有9975条数据都是要丢弃的。每次请求都要返回这么大的数据集,对应用程序的性能影响是非常大的。
一个好的解决方案是写一个分页的存储过程,例子2是一个用于对Northwind数据库orders表的分页存储过程。你只需要传当前页码,每页显示的条数两个参数进来,存储过程会返回相应的结果。
在服务器端,我专门写了一个分页的控件来处理数据的分页,在这里,我用了第一个方法,在一个存储过程里面返回了两个结果集:数据记录总数和要求的结果集。
返回的记录总数取决于要执行查询,例如,一个where条件可以限制返回的结果集的大小。因为在分页界面中必须要根据数据集记录的大小来计算总的页数,所以必须要返回结果集的记录数。例如,如果一共有1000000条记录,如果用where条件就可以过滤成只返回1000条记录,存储过程的分页逻辑应该知道返回那些需要显示的数据。
三、连接池
用TCP来连接你的应用程序与数据库是一件昂贵的事情(很费时的事情),微软的开发者可以通过用连接池来反复的使用数据库的连接。比起每次请求都用TCP来连一次数据库,连接池只有在不存在有效的连接时才新建一个TCP连接。当关闭一个连接的时候,它会被放到池中,它仍然会保持与数据库的连接,这样就可以减少与数据库的TCP连接次数。
当然,你要注意那些忘记关的连接,你应在每次用完连接后马上关闭它。我要强调的是:无论什么人说.NET Framework中的GC(垃圾收集器)总会在你用完连接对象后调用连接对象的Close或者Dispose方法显式的关闭你的连接。不要期望CLR会在你想象的时间内关掉连接,虽然CLR最终都要销毁对象和关闭边接,但是我们并不能确定它到底会在什么时候做这些事情。
要用连接池优化,有两条规则,第一,打开连接,处理数据,然后关闭连接。如果你必须在每次请求中多次打开或关闭连接,这好过一直打开一个边接,然后把它传到各个方法中。第二,用相同的连接字符串(或者用相同的用户标识,当你用集成认证的时候)。如果你没有用相同的连接字符串,如你用基于登录用户的连接字符串,这将不能利用连接池的优化功能。如果你用的是集成的论证,因为用户很多,所以你也不能充分利用连接池的优化功能。.NET CLR提供了一个数据性能计数器,它在我们需要跟踪程序性能特性的时候非常有用,当然也包括连接池的跟踪了。
无论你的应用程序什么时候要连在另一台机子的资源,如数据库,你都应该重点优化你连资源所花的时间,接收和发送数据的时间,以及往返回之间的次数。优化你的应用程序中的每一个处理点(process hop),它是提高你的应用的性能的出发点。
应用程序层包含与数据层连接,传送数据到相应的类的实例以及业务处理的逻辑。例如,在Community Server中,要组装一个Forums或者Threads集合,然后应用业务逻辑,如授权,更重要的,这里要完成缓存逻辑。
四、 ASP.NET缓存API
在写应用程序之前,你要做的第一件事是让应用程序最大化的利用ASP.NET的缓存功能。
如果你的组件是要在Asp.net应用程序中运行,你只要把System.Web.dll引用到你的项目中就可以了。然后用HttpRuntime.Cache属性就可访问Cache了(也可以通过Page.Cache或HttpContext.Cache访问)。
有以下几条缓存数据的规则。第一,数据可能会被频繁的被使用,这种数据可以缓存。第二,数据的访问频率非常高,或者一个数据的访问频率不高,但是它的生存周期很长,这样的数据最好也缓存起来。第三是一个常常被忽略的问题,有时候我们缓存了太多数据,通常在一台X86的机子上,如果你要缓存的数据超过800M的话,就会出现内存溢出的错误。所以说缓存是有限的。换名话说,你应该估计缓存集的大小,把缓存集的大小限制在10以内,否则它可能会出问题。在Asp.net中,如果缓存过大的话也会报内存溢出错误,特别是如果缓存大的DataSet对象的时候。
这里有几个你必须了解的重要的缓存机制。首先是缓存实现了“最近使用”原则( a least-recently-used algorithm),当缓存少的时候,它会自动的强制清除那些无用的缓存。其次 “条件依赖”强制清除原则(expiration dependencies),条件可以是时间,关键字和文件。以时间作为条件是最常用的。在asp.net2.0中增加一更强的条件,就是数据库条件。当数据库中的数据发生变化时,就会强制清除缓存。要更深入的了解数据库条件依赖请看Dino Esposito 在MSDN杂志2004年七月刊的Cutting Edge专栏文章。
五、 预请求缓存
在前面,我提到过即使我们只对某些地方作了一个小小的性能改进也可以获得大的性能提升,我非常喜欢用预请求缓存来提升程序的性能。
虽然Cache API设计成用来保存某段时间的数据,而预请求缓存只是保存某个时期的某个请求的内容。如果某个请求的访问频率高,而且这个请求只需要提取,应用,修改或者更新数据一次。那么就可以预缓存该请求。我们举个例子来说明。
在CS的论坛应用程序中,每一个页面的服务器控件都要求得到用于决定它的皮肤(skin)的自定义的数据,以决定用哪个样式表及其它的一些个性化的东西。这里面的某些数据可能要长时间的保存,有些时间则不然,如控件的skin数据,它只需要应用一次,而后就可以一直使用。
要实现预请求缓存,用Asp.net 的HttpContext类,HttpContext类的实例在每一个请求中创建,在请求期间的任何地方都可以通过HttpContext.Current属性访问。HttpContext类有一个Items集合属性,在请求期间所有的对象和数据都被添加到这个集合中缓存起来。和你用Cache缓存访问频率高数据一样,你可以用HttpContext.Items缓存那些每个请求都要用到的基础数据。它背后的逻辑很简单:我们向HttpContext.Items中添加一个数据,然后再从它里面读出数据。