在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
The paging of a large database resultset in Web applications is a well known problem. In short, you don't want all the results from your query to be displayed on a single Web page, so some sort of paged display is more appropriate. While it was not an easy task in the old ASP, the There are numerous articles and posts concerning this problem and several proposed solutions. My goal here is not to present you with an amazing solves-it-all procedure, but to optimize all the existing methods and provide you with a testing application so you can do evaluation on your own. Here is a good starting point article which describes many different approaches and provides some performance test results: GeneralizationThe three methods I decided to closely look into are the ones the author calls The second problem with allowing other sorting columns beside the PK column(s) is that if those columns are not indexed in some way, none of these methods will help. In all of them a paged source must be sorted first and the cost of using ordering by non-indexed column is immense for large tables. The response times are so high that all the procedures are practically unusable in this case (the response varies from couple of seconds to couple of minutes depending on the size of the tables and the starting record being fetched). The indexing of other columns brings more performance issues and may be undesirable, for example it might significantly slow you down in a situation where you have a lot of daily imports. TempTableThe first one I would comment on is the
Copy Code
TABLE #Temp ( ID int IDENTITY PRIMARY KEY, PK /* here goes PK type */ ) INSERT INTO #Temp SELECT PK FROM Table ORDER BY SortColumn SELECT ... FROM Table JOIN #Temp temp ON Table.PK = temp.PK ORDER BY temp.IDThe method can be optimized further by copying the rows to the temp table until the end paging row is reached ( SELECT TOP EndRow... ), but the point is that in the worst case – for a table with 1 million records you end up with 1 million records in a temp table as well. Considering all this and having looked upon the results in the article above, I decided to discard this method from my tests.
Asc-DescThis method uses default ordering in a subquery and then applies the reverse ordering. The principle goes like this
Copy Code
TABLE ( PK /* PK Type */ NOT NULL PRIMARY ) INSERT INTO @temp SELECT TOP @PageSize PK FROM ( SELECT TOP (@StartRow + @PageSize) PK, SortColumn /*If sorting column is defferent from the PK, SortColumn mustFull Code – Paging_Asc_Desc RowCountThe base logic of this method relies on the SQL
Copy Code
the type of the sorting column */ SET ROWCOUNT @StartRow SELECT @Sort = SortColumn FROM Table ORDER BY SortColumn SET ROWCOUNT @PageSize SELECT ... FROM Table WHERE SortColumn >= @Sort ORDER BY SortColumnFull Code – Paging_RowCount SubQueryThere are 2 more methods I’ve taken into consideration, and they come from different resources. The first one is well known triple query or the
Copy Code
IN (SELECT TOP @PageSize PK FROM Table WHERE PK NOT IN (SELECT TOP @StartRow PK FROM Table ORDER BY SortColumn) ORDER BY SortColumn) ORDER BY SortColumnFull Code – Paging_SubQuery CursorI’ve found the last method while browsing through the Google groups, you can find the original thread here. This method uses a server-side dynamic cursor. A lot of people tend to avoid cursors, they usually have poor performance because of their non-relational, sequential nature. The thing is that paging IS a sequential task and whatever method you use you have to somehow reach the starting row. In all the previous methods this is done by selecting all rows preceding the starting row plus the desired rows and then discarding all the preceding rows. Dynamic cursor has the
Copy Code
PK Type */ DECLARE @tblPK TABLE ( PK /* PK Type */ NOT NULL PRIMARY KEY ) DECLARE PagingCursor CURSOR DYNAMIC READ_ONLY FOR SELECT @PK FROM Table ORDER BY SortColumn OPEN PagingCursor FETCH RELATIVE @StartRow FROM PagingCursor INTO @PK WHILE @PageSize > 0 AND @@FETCH_STATUS = 0 BEGIN INSERT @tblPK(PK) VALUES(@PK) FETCH NEXT FROM PagingCursor INTO @PK SET @PageSize = @PageSize - 1 END CLOSE PagingCursor DEALLOCATE PagingCursor SELECT ... FROM Table JOIN @tblPK temp ON Table.PK = temp.PKFull Code – Paging_Cursor Generalization of Complex QueriesAs pointed out before, all the procedures are generalized with dynamic SQL, thus, in theory, they can work with any kind of complex query. Here is a complex query sample that works with
Copy Code
AS Customer, Customers.Address + ', ' + Customers.City + ', ' +The paging stored procedure call that returns the second page looks like this
Copy Code
EXEC ProcedureName /* Tables */ 'Customers INNER JOIN Orders ON Customers.CustomerID = Orders.CustomerID INNER JOIN [Order Details] ON Orders.OrderID = [Order Details].OrderID', /* PK */ 'Customers.CustomerID', /* ORDER BY */ 'Customers.ContactName DESC, Customers.Address DESC', /* PageNumber */ 2, /* Page Size */ 10, /* Fields */ 'Customers.ContactName AS Customer, Customers.Address + '', '' + Customers.City + '', '' + Customers.Country Note that in the original query, aliases are used in the The Performance TestingI used these 4 methods in my tests, if you have a better one, I’d be glad to know about it. Nevertheless, I wanted to compare these methods and measure their performance. The first thought was to write an ASP.NET test application with paged DataGrid and then measure page response. Still, this wouldn’t reflect the true response time of the stored procedures, so the console application seemed more appropriate. I also included a web application, not for performance testing, but rather as an example of how DataGrid custom paging works with these stored procedures. They are both incorporated in the PagingTest Solution. I used the auto generated large table for my tests and inserted around 500 000 records in it. If you don’t have a large table to experiment on, you can download the script for a table design and stored procedure for data generation here. I didn't want an identity column for my PK, I used the The idea behind performance testing was to call a specific stored procedure many times through a loop and then measure the average response time. Also, in order to remove caching deviations and to model the real situation more accurately – multiple calls to a stored proc with the same page fetched each time seemed inappropriate. Thus, a random sequence of the same stored procedure with a set of different page numbers was required. Of course, a set of different page numbers assumes fixed number of pages (10 – 20) where each page would be fetched many times, but in a random sequence. It’s not hard to notice that response times depend on the distance of the fetched page from the beginning of the resultset. The further the starting record is, more records need to be skipped. This is the reason I didn’t include first 20 pages in my random sequence. Instead I used the set of 2N pages. A loop was set to a (number of different pages)*1000. So, every page was fetched around 1000 times (more or less because of a random distribution). |
请发表评论