【问题标题】:Is there any performance issue using Row_Number to implement table paging in Sql Server 2008?在 Sql Server 2008 中使用 Row_Number 实现表分页是否存在任何性能问题?
【发布时间】:2011-01-19 11:25:30
【问题描述】:

我想用这种方法实现表分页:

SET @PageNum = 2;
SET @PageSize = 10;

WITH OrdersRN AS
(
    SELECT ROW_NUMBER() OVER(ORDER BY OrderDate, OrderID) AS RowNum
          ,*
      FROM dbo.Orders
)

SELECT * 
  FROM OrdersRN
 WHERE RowNum BETWEEN (@PageNum - 1) * @PageSize + 1 
                  AND @PageNum * @PageSize
 ORDER BY OrderDate ,OrderID;

有什么我应该注意的吗? 表有数百万条记录。

谢谢。

编辑: 在使用建议的MAXROWS 方法一段时间后(效果非常快),我不得不切换回ROW_NUMBER 方法,因为它具有更大的灵活性。到目前为止,我对它的速度也很满意(我正在使用 View 拥有超过 100 万条记录和 10 列)。要使用任何类型的查询,我使用以下修改:

PROCEDURE [dbo].[PageSelect] 
(
  @Sql nvarchar(512),
  @OrderBy nvarchar(128) = 'Id',
  @PageNum int = 1,
  @PageSize int = 0    
)
AS
BEGIN
SET NOCOUNT ON

 Declare @tsql as nvarchar(1024)
 Declare @i int, @j int

 if (@PageSize <= 0) OR (@PageSize > 10000)
  SET @PageSize = 10000  -- never return more then 10K records

 SET @i = (@PageNum - 1) * @PageSize + 1 
 SET @j = @PageNum * @PageSize

 SET @tsql = 
 'WITH MyTableOrViewRN AS
 (
  SELECT ROW_NUMBER() OVER(ORDER BY ' + @OrderBy + ') AS RowNum
     ,*
    FROM MyTableOrView
    WHERE ' + @Sql  + '

 )
 SELECT * 
  FROM MyTableOrViewRN 
  WHERE RowNum BETWEEN ' + CAST(@i as varchar) + ' AND ' + cast(@j as varchar)

 exec(@tsql)
END

如果您使用此过程,请确保您阻止了 sql 注入。

【问题讨论】:

  • Pony,我对这个答案不太满意,主要是因为它甚至没有提到 Row_Number() ..... 问题又是:我正在使用 Row_Number()。你能告诉我它与其他方法相比的性能(所以,不要给我提供其他方法)
  • 顺便说一句,Pony 我觉得这样的言论非常粗鲁。我确信我知道我的问题的好答案是什么,我不需要你告诉我。典型的amdin BS。

标签: sql-server paging


【解决方案1】:

最近,我在星型模式的数据仓库环境中使用了分页。我发现当我将 CTE 限制为仅查询确定ROW_NUMBER 所需的行时,性能非常好。我让 CTE 返回了 ROW_NUMBER 以及有助于确定行号的其他行的主键。

在主查询中,我引用ROW_NUMBER 进行分页,然后根据来自CTE 的其他主键加入其他表。我发现只对外部查询中满足WHERE 子句的行执行连接,节省了大量时间。

【讨论】:

  • 这应该可以减少问题。试试看,然后看执行计划。
【解决方案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

【讨论】:

  • 将整个表复制到临时表中...没有索引?是的,那会很慢。回复慢。很难想象一个更糟糕的方法,TBH。
  • 另外,请注意问题中的“Row_Number”问题。虽然我觉得这没什么用(无意冒犯),但我会给你一个加号,只是为了让 OMG 小马和他的朋友们开心。
【解决方案3】:

实际上我已经写过几次了; ROW_NUMBER 是迄今为止最灵活且易于使用的,并且性能良好,但对于超大型数据集,它并不总是最佳。 SQL Server 仍然需要对数据进行排序,而且排序会变得非常昂贵。

有一个 different approach here 使用几个变量和 SET ROWCOUNT 并且速度非常快,前提是您有正确的索引。它很旧,但据我所知,它仍然是最有效的。基本上你可以用SET ROWCOUNT 做一个完全幼稚的SELECT,SQL Server 能够优化掉大部分实际工作;计划和成本最终类似于两个MAX/MIN 查询,这通常比单个窗口查询快得多。对于非常大的数据集,运行时间不到 1/10。

话虽如此,当人们询问如何实现分页或分组最大值之类的事情时,我仍然总是推荐ROW_NUMBER,因为它很容易使用。如果您开始注意到 ROW_NUMBER 的减速,我只会开始寻找类似上述的替代方案。

【讨论】:

  • 第一个可接受的答案。谢谢m8。我不需要最好的。我需要好的。
  • 我在 ROWCOUNT 中使用了这种方法,我对它非常满意,它非常快。但是,当我有带有非标识列的自定义 ORDER BY 语句时,我无法使其工作。你知道解决办法吗?
  • @majkinetor:您的意思是要按 ID 以外的字段进行排序/分页,还是表根本没有 ID 列或顺序键?
  • 实际上,我想这并不重要...它肯定适用于非 ID 列,但您需要更改 所有内容 - 我怀疑您更改了ORDER BY 在两个查找中,但仍选择按 ID 保存和过滤;您需要更改查询以按实际排序列保存和过滤。
  • 问题是“保留最后一个索引”,以便知道从哪里继续下一页。如果您没有身份列,您不知道从哪里继续。例如,假设我要返回 100 行,每列包含单个常量。即使第二列是标识,我也没有找到使用它来标记下一个要返回的子集的方法。因此,我切换到 ROW_NUMBER。
猜你喜欢
  • 2012-02-14
  • 2013-08-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-01
  • 1970-01-01
相关资源
最近更新 更多