读书笔记:.NET数据存取架构(6)
15:数据分页
对数据进行分页是分布式应用程序中的共同要求。例如,可能要向用户展示一个书籍清单,此时,同时显示整个清单可能被禁止。用户将希望对数据执行熟悉的操作,如查看下一页或上一页数据,或者跳到该清单的开头或结尾。
本节将讨论用于实现此功能的选项,以及每个选项对可伸缩性和性能的影响。
比较几种选择
数据分页的选择包括:
| • |
通过 SqlDataAdapter 的 Fill 方法,用查询的一系列结果填充 DataSet |
| • |
通过 COM 互操作性使用 ADO,并且使用服务器端游标 |
| • |
使用存储过程来手动实现数据分页 |
数据分页的最佳选择取决于下面列出的因素:
| • |
可伸缩性要求 |
| • |
性能要求 |
| • |
网络带宽 |
| • |
数据库服务器内存和功能 |
| • |
中间层服务器内存和功能 |
| • |
希望进行分页的、由查询返回的行数 |
| • |
数据页的大小 |
性能测试表明,使用存储过程的手动方法在广泛的压力级别下提供了最佳性能。不过,因为手动方法在服务器上完成其工作,所以如果网站的大部分功能都依赖于数据分页功能,则服务器压力级别可能成为一个重要问题。要确保该方法适合您的特定环境,应该针对您的特定要求测试所有选择。
下面讨论了各种选择。
使用 SqlDataAdapter 的 Fill 方法
如上所述,可通过 SqlDataAdapter 用数据库中的数据来填充 DataSet。重载的 Fill 方法之一(如以下代码所示)采用两个整数索引值作为参数。
public int Fill( DataSet dataSet, int startRecord, int maxRecords, string srcTable );
startRecord 值表示起始记录的从零开始的索引。maxRecords 值表示要复制到新 DataSet 中的记录数(从 startRecord 开始)。
SqlDataAdapter 在内部使用 SqlDataReader 来执行查询并返回结果。SqlDataAdapter 读取结果并基于从 SqlDataReader 中读取的数据创建一个 DataSet。SqlDataAdapter 通过 startRecord 和 maxRecords 将所有结果复制到刚生成的 DataSet 中,并且丢弃它不需要的结果。这意味着可能会将大量不需要的数据通过网络提取到数据访问客户端,而这正是该方法的主要缺点。
例如,如果有 1,000 条记录,并且只需要记录 900 到 950,则仍然会将前面的 899 条记录通过网络提取到客户端并丢弃。这一开销对于小型结果集而言可能很小,但当您对较大的数据集进行分页时,这一开销可能会很大。
使用 ADO
另一种实现分页的选择是使用基于 COM 的 ADO 进行分页。该选项背后的主要动机是获取对服务器端游标的访问,该游标通过 ADO Recordset 对象公开。可以将 Recordset 游标位置设置为 adUseServer。如果您的 OLE DB 提供程序支持它(SQLOLEDB 就支持),这将导致对服务器端游标的使用。然后,您可以使用该游标直接导航到起始记录,而无须将所有记录通过网络提取到数据访问客户端代码。
该方法有以下两个主要缺点:
| • |
大多数情况下,您需要将在 Recordset 对象中返回的记录转换到 DataSet 中,以便在客户端托管代码中使用。尽管 OleDbDataAdapter 的确重载了 Fill 方法,采用 ADO Recordset 对象作为参数并将其转换到 DataSet 中,但没有用来指定起始和结束记录的工具。唯一现实的选择是移动到 Recordset 对象中的起始记录,依次处理各个记录,并将数据手动复制到手动生成的新 DataSet 中。这样做(特别是通过 COM Interop 调用的开销)有可能完全抵消不通过网络提取额外数据的优点,尤其是对小型 DataSet 而言。 |
| • |
在从服务器提取所需数据花费的这段时间内,您需要使连接和服务器端游标保持打开状态。在数据库服务器上打开和维护时,游标通常是一种昂贵的资源。尽管该选择可以提高性能,它还可能由于在服务器上长期消耗宝贵的资源而降低可伸缩性。 |
使用手动实现
本节讨论的最后一个对数据进行分页的选择是,通过使用存储过程为应用程序手动实现分页功能。对于包含唯一键的表而言,可以相对容易地实现存储过程。对于不包含唯一键的表(您不应该具有许多这样的表)而言,该过程更复杂一些。
对包含唯一键的表进行分页
如果表包含唯一键,可以在 WHERE 子句中使用该键来创建从特定行开始的结果集。将这种方法与用于限制结果集大小的 SET ROWCOUNT 语句或 SQL Server TOP 语句结合起来,可以提供一种有效的分页机制。下面的存储过程代码阐明了这一方法:
CREATE PROCEDURE GetProductsPaged @lastProductID int, @pageSize int AS SET ROWCOUNT @pageSize SELECT * FROM Products WHERE [standard search criteria] AND ProductID > @lastProductID ORDER BY [Criteria that leaves ProductID monotonically increasing] GO
该存储过程的调用方简单地维护 lastProductID 值,并且在连续调用之间将其递增或递减所选的页面大小。
对不包含唯一键的表进行分页
如果您要对其进行分页的表不包含唯一键,可以考虑通过某种方法(例如,使用标识列)添加一个唯一键。这将使您能够实现前面讨论的分页解决方案。
对于不包含唯一键的表,只要您能够通过将属于结果集的其他两个或更多个字段组合起来生成唯一性,则仍然能够实现有效的分页解决方案。
例如,请考虑下表:
| Col1 | Col2 | Col3 | 其他列§ |
|
A |
1 |
W |
§ |
|
A |
1 |
X |
§ |
|
A |
1 |
Y |
§ |
|
A |
1 |
Z |
§ |
|
A |
2 |
W |
§ |
|
A |
2 |
X |
§ |
|
B |
1 |
W |
§ |
|
B |
1 |
X |
§ |
对于该表,可以通过组合 Col1、Col2 和 Col3 来生成唯一性。因此,可以通过使用以下存储过程中阐明的方法来实现分页机制。
CREATE PROCEDURE RetrieveDataPaged @lastKey char(40), @pageSize int AS SET ROWCOUNT @pageSize SELECT Col1, Col2, Col3, Col4, Col1+Col2+Col3 As KeyField FROM SampleTable WHERE [Standard search criteria] AND Col1+Col2+Col3 > @lastKey ORDER BY Col1 ASC, Col2 ASC, Col3 ASC GO
客户端保持由存储过程返回的 KeyField 列的最后值,并将其重新插入到存储过程中以控制表分页。
尽管手动实现增加了数据库服务器的负担,但它能够避免通过网络传递不必要的数据。性能测试表明,该方法能够在广泛的压力级别下正常工作。然而,根据与数据分页功能有关的网站工作量的不同,在服务器上执行手动分页可能会影响应用程序的可伸缩性。您应该在自己的环境中运行性能测试,以便找到适合于特定应用程序方案的最佳方法。
posted on 2004-06-28 13:59 木人(我现在不是老大) 阅读(424) 评论(0) 收藏 举报
浙公网安备 33010602011771号