- ·上一篇文章:编写高性能Web应用程序的10个技巧(3)
- ·下一篇文章:编写高性能Web应用程序的10个技巧(1)
编写高性能Web应用程序的10个技巧(2)
1CREATE PROCEDURE northwind_OrdersPaged
2(
3 @PageIndex int,
4 @PageSize int
5)
6AS
7BEGIN
8DECLARE @PageLowerBound int
9DECLARE @PageUpperBound int
10DECLARE @RowsToReturn int
11
12-- First set the rowcount
13SET @RowsToReturn = @PageSize * (@PageIndex + 1)
14SET ROWCOUNT @RowsToReturn
15
16-- Set the page bounds
17SET @PageLowerBound = @PageSize * @PageIndex
18SET @PageUpperBound = @PageLowerBound + @PageSize + 1
19
20-- Create a temp table to store the select results
21CREATE TABLE #PageIndex
22(
23 IndexId int IDENTITY (1, 1) NOT NULL,
24 OrderID int
25)
26
27-- Insert into the temp table
28INSERT INTO #PageIndex (OrderID)
29SELECT
30 OrderID
31FROM
32 Orders
33ORDER BY
34 OrderID DESC
35
36-- Return total count
37SELECT COUNT(OrderID) FROM Orders
38
39-- Return paged results
40SELECT
41 O.*
42FROM
43 Orders O,
44 #PageIndex PageIndex
45WHERE
46 O.OrderID = PageIndex.OrderID AND
47 PageIndex.IndexID > @PageLowerBound AND
48 PageIndex.IndexID < @PageUpperBound
49ORDER BY
50 PageIndex.IndexID
51
52END
53
54
在社区服务期中,我们写了一个分页服务端控件来做这些数据分页。您会发现我在使用技巧1中讨论过的思想,从一个存储过程返回两个结果集:纪录总数和请求的数据。
返回的记录总数可以根据执行的请求而有所不同。例如,一个WHERE分句可以用来约束返回的数据。我们必须知道要返回的记录总数,以计算要在分页UI中显示的总的页数。例如,如果有1,000,000条总的记录数,而一个WHERE分句用来把这些记录过滤为1,000条记录,分页逻辑需要知道总的记录数来恰当的提交分页UI。
技巧 3——连接池
在您的Web应用程序和SQL Server之间建立TCP连接会是一个昂贵的操作。Microsoft的开发者们已经利用连接池有一段时间了,这允许他们重用与数据库的连接。与其为每个请求建立一个新的TCP连接,还不如只有在连接池中没有一个可用的连接的时候才建立一个新的连接。当连接关闭后,它返回到连接池中——它还保持着与数据库的连接,而不是完全销毁那个TCP连接。
当然您需要小心泄露的连接。总是关闭您的连接在您使用完它们时。我重复一遍:不管谁说了关于Microsoft .NET框架的垃圾回收机制的什么话,当您使用完时,您务必总是对您的连接显式调用Close或者Dispose方法。不要相信通用语言运行时(CLR)会在一个预定的时间为您清理和关闭您的连接。CLR会最终销毁类并且强迫连接关闭,但您不能保证什么时候在对象上的垃圾回收机制会真正执行。
要想使用连接池达到最佳效果,您需要遵循几条规则。第一,打开一个连接,完成工作,然后关闭连接。如果您不得不(最好应用技巧1)为每个请求打开和关闭几次连接也是可以的,这比一直开着连接然后把它传递给几个不同的方法要好得多。第二,使用同一个连接字符串(如果您在使用集成身份认证,当然还需要有相同的线程标识)。如果您不使用同一个连接字符串,例如基于登录的用户的不同自定义连接字符串,您就不能得到连接池提供的相同的最优值。而且如果您在模仿大量的用户时使用了集成身份验证,您的连接池的效率也会降低很多。在尝试跟踪任何与连接池有关的性能问题时,.NET CLR数据性能计数器会很有用的。
不论何时您的应用程序连接一个资源,例如一个数据库,或者在另一个进程中运行,您都应该通过把注意力集中到连接到资源所花费的时间上,发送和接受数据花费的时间,还有往返与数据库的次数来进行优化。优化您的应用程序中的任何类型的进程跳转(process hop)都是开始达到更好性能的第一步。
应用层包含连接到您的数据层的逻辑,并且把数据转换为有意义的类实例和逻辑过程。例如,在社区服务器中,这里是您生成一个论坛或者线程集合,并且应用业务规则例如许可的地方;更重要的是这里是执行缓冲逻辑的地方。
技巧 4——ASP.NET缓冲API
在您开始编写应用程序的第一行代码之前要考虑的第一件事情是,架构应用层来最大化并且利用ASP.NET的缓存特性。
如果您的组件运行在一个ASP.NET应用程序之中,您只需要在您的应用程序项目中简单的引用System.Web.dll就可以了。当您需要访问缓存时,使用HttpRuntime.Cache属性(这个对象也可以通过Page.Cache和HttpContext.Cache来访问)。
使用缓存数据有几条原则。第一,如果数据可以多次使用,那么缓存它就是一个好的选择。第二,如果数据是通用的而不是给特定的请求或者用户使用的,那么缓存它就是一个非常好的选择。如果数据是用户或者请求特定的,但是他的生存期是很长的,那么它也可以被缓存,但是可能不会经常使用到。第三,一个经常被忽视的原则是,有时候您可以缓存的太多了。通常在一台x86计算机上,为了减少发生内存不足(out-of-memory)错误的可能性,您会希望运行一个使用不超过800MB私有字节的进程。因此,缓存应该受到限制。换句话说,您可能需要重新使用一次计算的结果,但是如果那个计算需要十个参数,您可能需要尝试缓存10个排列,而这可能会给您带来麻烦。由于过度缓存引起的内存不足错误是ASP.NET中最常见的,特别是对于大数据集的情况。
缓存有几个极佳的功能,您需要对它们有所了解。首先,缓存会实现最近最少使用的算法,使得 ASP.NET 能够在内存运行效率较低的情况下强制缓存清除——从缓存自动删除未使用过的项目。第二,缓存支持可以强制失效的过期依赖项。这些依赖项包括时间、键和文件。时间经常会用到,但是对于 ASP.NET 2.0,引入了一个功能更强的新失效类型:数据库缓存失效。它指的是当数据库中的数据发生变化时自动删除缓存中的项。有关数据库缓存失效的详细信息,请参阅 MSDN Magazine 2004 年 7 月的 Dino Esposito Cutting Edge 专栏。要了解缓存的体系结构,请参阅图 3。
技巧 5 — 每请求缓存
在本文前面部分,我提到了对经常遍历代码路径的一些小改善可以获得较大的整体性能收益。对于这些小改善,其中有一个绝对是我的最爱,我将其称之为“每请求缓存”。
2(
3 @PageIndex int,
4 @PageSize int
5)
6AS
7BEGIN
8DECLARE @PageLowerBound int
9DECLARE @PageUpperBound int
10DECLARE @RowsToReturn int
11
12-- First set the rowcount
13SET @RowsToReturn = @PageSize * (@PageIndex + 1)
14SET ROWCOUNT @RowsToReturn
15
16-- Set the page bounds
17SET @PageLowerBound = @PageSize * @PageIndex
18SET @PageUpperBound = @PageLowerBound + @PageSize + 1
19
20-- Create a temp table to store the select results
21CREATE TABLE #PageIndex
22(
23 IndexId int IDENTITY (1, 1) NOT NULL,
24 OrderID int
25)
26
27-- Insert into the temp table
28INSERT INTO #PageIndex (OrderID)
29SELECT
30 OrderID
31FROM
32 Orders
33ORDER BY
34 OrderID DESC
35
36-- Return total count
37SELECT COUNT(OrderID) FROM Orders
38
39-- Return paged results
40SELECT
41 O.*
42FROM
43 Orders O,
44 #PageIndex PageIndex
45WHERE
46 O.OrderID = PageIndex.OrderID AND
47 PageIndex.IndexID > @PageLowerBound AND
48 PageIndex.IndexID < @PageUpperBound
49ORDER BY
50 PageIndex.IndexID
51
52END
53
54
在社区服务期中,我们写了一个分页服务端控件来做这些数据分页。您会发现我在使用技巧1中讨论过的思想,从一个存储过程返回两个结果集:纪录总数和请求的数据。
返回的记录总数可以根据执行的请求而有所不同。例如,一个WHERE分句可以用来约束返回的数据。我们必须知道要返回的记录总数,以计算要在分页UI中显示的总的页数。例如,如果有1,000,000条总的记录数,而一个WHERE分句用来把这些记录过滤为1,000条记录,分页逻辑需要知道总的记录数来恰当的提交分页UI。
技巧 3——连接池
在您的Web应用程序和SQL Server之间建立TCP连接会是一个昂贵的操作。Microsoft的开发者们已经利用连接池有一段时间了,这允许他们重用与数据库的连接。与其为每个请求建立一个新的TCP连接,还不如只有在连接池中没有一个可用的连接的时候才建立一个新的连接。当连接关闭后,它返回到连接池中——它还保持着与数据库的连接,而不是完全销毁那个TCP连接。
当然您需要小心泄露的连接。总是关闭您的连接在您使用完它们时。我重复一遍:不管谁说了关于Microsoft .NET框架的垃圾回收机制的什么话,当您使用完时,您务必总是对您的连接显式调用Close或者Dispose方法。不要相信通用语言运行时(CLR)会在一个预定的时间为您清理和关闭您的连接。CLR会最终销毁类并且强迫连接关闭,但您不能保证什么时候在对象上的垃圾回收机制会真正执行。
要想使用连接池达到最佳效果,您需要遵循几条规则。第一,打开一个连接,完成工作,然后关闭连接。如果您不得不(最好应用技巧1)为每个请求打开和关闭几次连接也是可以的,这比一直开着连接然后把它传递给几个不同的方法要好得多。第二,使用同一个连接字符串(如果您在使用集成身份认证,当然还需要有相同的线程标识)。如果您不使用同一个连接字符串,例如基于登录的用户的不同自定义连接字符串,您就不能得到连接池提供的相同的最优值。而且如果您在模仿大量的用户时使用了集成身份验证,您的连接池的效率也会降低很多。在尝试跟踪任何与连接池有关的性能问题时,.NET CLR数据性能计数器会很有用的。
不论何时您的应用程序连接一个资源,例如一个数据库,或者在另一个进程中运行,您都应该通过把注意力集中到连接到资源所花费的时间上,发送和接受数据花费的时间,还有往返与数据库的次数来进行优化。优化您的应用程序中的任何类型的进程跳转(process hop)都是开始达到更好性能的第一步。
应用层包含连接到您的数据层的逻辑,并且把数据转换为有意义的类实例和逻辑过程。例如,在社区服务器中,这里是您生成一个论坛或者线程集合,并且应用业务规则例如许可的地方;更重要的是这里是执行缓冲逻辑的地方。
技巧 4——ASP.NET缓冲API
在您开始编写应用程序的第一行代码之前要考虑的第一件事情是,架构应用层来最大化并且利用ASP.NET的缓存特性。
如果您的组件运行在一个ASP.NET应用程序之中,您只需要在您的应用程序项目中简单的引用System.Web.dll就可以了。当您需要访问缓存时,使用HttpRuntime.Cache属性(这个对象也可以通过Page.Cache和HttpContext.Cache来访问)。
使用缓存数据有几条原则。第一,如果数据可以多次使用,那么缓存它就是一个好的选择。第二,如果数据是通用的而不是给特定的请求或者用户使用的,那么缓存它就是一个非常好的选择。如果数据是用户或者请求特定的,但是他的生存期是很长的,那么它也可以被缓存,但是可能不会经常使用到。第三,一个经常被忽视的原则是,有时候您可以缓存的太多了。通常在一台x86计算机上,为了减少发生内存不足(out-of-memory)错误的可能性,您会希望运行一个使用不超过800MB私有字节的进程。因此,缓存应该受到限制。换句话说,您可能需要重新使用一次计算的结果,但是如果那个计算需要十个参数,您可能需要尝试缓存10个排列,而这可能会给您带来麻烦。由于过度缓存引起的内存不足错误是ASP.NET中最常见的,特别是对于大数据集的情况。
缓存有几个极佳的功能,您需要对它们有所了解。首先,缓存会实现最近最少使用的算法,使得 ASP.NET 能够在内存运行效率较低的情况下强制缓存清除——从缓存自动删除未使用过的项目。第二,缓存支持可以强制失效的过期依赖项。这些依赖项包括时间、键和文件。时间经常会用到,但是对于 ASP.NET 2.0,引入了一个功能更强的新失效类型:数据库缓存失效。它指的是当数据库中的数据发生变化时自动删除缓存中的项。有关数据库缓存失效的详细信息,请参阅 MSDN Magazine 2004 年 7 月的 Dino Esposito Cutting Edge 专栏。要了解缓存的体系结构,请参阅图 3。
技巧 5 — 每请求缓存
在本文前面部分,我提到了对经常遍历代码路径的一些小改善可以获得较大的整体性能收益。对于这些小改善,其中有一个绝对是我的最爱,我将其称之为“每请求缓存”。
