【问题标题】:T-sql: how to perform optimised Paging?T-sql:如何执行优化的分页?
【发布时间】:2011-01-13 22:20:15
【问题描述】:

我编写了以下代码,它工作正常,但如果表包含一百万条记录,则需要大约 3 秒才能完成。有没有办法优化下面的代码。

DBCC DROPCLEANBUFFERS; 
DBCC FREEPROCCACHE;

DECLARE @Page_Size int;
DECLARE @Page_Number int;
DECLARE @Lower_Bound int;
DECLARE @Upper_Bound int;

SET @Page_Size = 30;
SET @Page_Number = 30000;
SET @Lower_Bound = (@Page_Number - 1) * @Page_Size;
--SET @Upper_Bound = @Page_Number * @Page_Size;


WITH Customers AS--(Row_Numbr, Record_Id, First_Name, 
        Middle_Name, Last_Name, Email, Telephone) AS 
(

    SELECT ROW_NUMBER() 
        OVER 
         (ORDER BY Account.Customer.Record_Id) AS Row_Numbr, * 
    FROM Account.Customer 
)

SELECT top(@Page_Size) * 
FROM Customers 
WHERE Row_Numbr > @Lower_Bound-- 
    AND Row_Numbr <= @Upper_Bound -- This is suppose to be faster
--SELECT * FROM Customers 
--WHERE Row_Numbr > @Lower_Bound  
--   AND Row_Numbr <= @Upper_Bound

【问题讨论】:

  • 执行计划是怎么说的?
  • 你有什么索引?你能发布创建表脚本吗?
  • 主键是Record_Id所以它是表索引,执行计划说“97%用于聚集索引扫描”,“2%用于过滤”。

标签: sql-server performance tsql pagination


【解决方案1】:

首先,为什么是 DBCC DROPCLEANBUFFERS; ?这是缓冲池的硬冷重置。除非你想测量和调整你的硬盘 IO 性能,否则没人关心冷缓存的性能。这不是您的系统将如何工作。在缓冲池中缓存页面是数据库中最关键的性能方面,你可以把它去掉。这就像出现在一辆没有引擎的法拉利上,问为什么这么慢。对于性能测量,您应该完全相反:运行他的查询 4-5 次以预热缓存,然后进行测量。

第二,你的表结构是什么?表Account.Customer 表簇索引是否按Record_id 排序?如果不是,无论您如何表达您的 T-SQL,您都永远无法获得您想要的性能。

最后但同样重要的是,你有什么系统?它是否有足够的 RAM 将整个数据库缓存在内存中?如果没有,请购买更多 RAM。是否有其他进程争夺内存,例如 IIS/Asp?如果是,请将它们踢出到自己的服务器上,如果性能很重要,您应该 never ever run the database on the same host as the web server

对于另一种快速分页,请考虑键集驱动的解决方案:

/* moving up */
SELECT top(@Page_Size) * 
FROM Account.Customer  
WHERE Record_Id > @lastPageRecordId
ORDER BY Record_Id;

/* moving down */
SELECT top(@Page_Size) * 
FROM Account.Customer  
WHERE Record_Id < @firstPageRecordId
ORDER BY Record_Id DESC;

键集驱动的解决方案可以直接查找最后一个位置,然后使用聚集索引键位置进行范围扫描下一页/上一页。分页逻辑(状态)必须记住要显示的页面上的最后一个键和第一个键才能从那里继续,而不是记住页码。

基于行数的解决方案(以及 MySQL 中的 LIMIT)效率低于基于键集的解决方案,因为它们总是必须对记录进行计数以定位自己,而不是像键集那样直接寻找位置。

【讨论】:

  • 但是 Record_Id 可以是 1, 3 , 7, 10, 11, 12, 13, 17。页面不相等,最后一页可能不会覆盖整个表。在这种情况下,如何获得准确的@lastPageRecordId?刚才我明白了您想使用 Record_Id 索引来提高性能,所以您希望我更改 WITH 子句并使用 Record_Id 而不是 Row_Number,我不知道如何更改 WITH 子句!我在关注你吗?
  • 假设你想显示 3 页。第一次你说 TOP(3)... ORDER BY RecordId 你得到 1,3,7。对于下一页,您要求 TOP(3).. WHERE RecordId>7 ...ORDER BY ... 你得到 10,11,12。往下走,你要求 TOP(3) ... WHERE RecordId 12 ... ORDER BY ... 你得到 13,17。您将页面上的第一个和最后一个 RecordId 用作,而不是排名。
  • @Remus 嗨,keyset 解决方案比分页更快吗?我尝试根据上面的代码创建存储存储过程,但没有返回任何结果。有任何想法吗?创建 Proc PagingSample ((at)Page_Size int, (at)firstPageRecordId int) AS begin SELECT top((at)Page_Size) * FROM dbo.data WHERE (at)(at)IDENTITY
  • 有趣的是,我不知道这也被称为“键集驱动的解决方案”。我听说人们称之为seek method, as I described in a recent blog post。无论如何,很高兴看到有人提出这个建议。这种情况很少发生
  • “没人关心冷缓存的性能”。这肯定取决于它对用户的结果的频率。如果您的系统运行很热,那么您所说的是正确的,但是如果就缓存中的内容以及给定用户想要阅读的内容而言,它的冷到温和,那么冷读将成为一个相对常见的用例并且很重要。至少,就销售和最初的客户感知而言,冷读可能很重要,除非您的 init 特别升温。在大型集成作业会给缓存带来压力的情况下,冷读可能会更频繁地发生。
【解决方案2】:

我使用这个存储过程:

CREATE PROCEDURE sp_PagedItems
    (
     @Page int,
     @RecsPerPage int
    )
AS

-- We don't want to return the # of rows inserted
-- into our temporary table, so turn NOCOUNT ON
SET NOCOUNT ON


--Create a temporary table
CREATE TABLE #TempItems
(
    ID int IDENTITY,
    Name varchar(50),
    Price currency
)


-- Insert the rows from tblItems into the temp. table
INSERT INTO #TempItems (Name, Price)
SELECT Name,Price FROM tblItem ORDER BY Price

-- Find out the first and last record we want
DECLARE @FirstRec int, @LastRec int
SELECT @FirstRec = (@Page - 1) * @RecsPerPage
SELECT @LastRec = (@Page * @RecsPerPage + 1)

-- Now, return the set of paged records, plus, an indiciation of we
-- have more records or not!
SELECT *,
       MoreRecords =
    (
     SELECT COUNT(*)
     FROM #TempItems TI
     WHERE TI.ID >= @LastRec
    )
FROM #TempItems
WHERE ID > @FirstRec AND ID < @LastRec


-- Turn NOCOUNT back OFF
SET NOCOUNT OFF

【讨论】:

    【解决方案3】:

    如果有人使用 SQL Server 2012 - 在 ORDER BY 子句中添加了一项新功能,用于查询优化一组数据,使任何使用 T-SQL 编写的人以及整个执行的数据分页工作更轻松在 SQL Server 中进行计划。参考here

    【讨论】:

      猜你喜欢
      • 2018-08-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-29
      • 2013-05-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多