3

我正在尝试使用分页,我在 SO 中得到了完美的链接

https://stackoverflow.com/a/109290/1481690

SELECT  *
FROM    ( SELECT    ROW_NUMBER() OVER ( ORDER BY OrderDate ) AS RowNum, *
          FROM      Orders
          WHERE     OrderDate >= '1980-01-01'
        ) AS RowConstrainedResult
WHERE   RowNum >= 1
    AND RowNum < 20
ORDER BY RowNum

完全相同的查询正在尝试与我的内部查询中的几个表的附加连接一起使用。

在以下情况下几乎没有性能问题

WHERE   RowNum >= 1
    AND RowNum < 20  ==>executes faster approx 2 sec


    WHERE   RowNum >= 1000
    AND RowNum < 1010      ==>  more time  approx 10 sec

    WHERE   RowNum >= 30000
    AND RowNum < 30010    ==> more time approx 17 sec

每次我选择 10 行,但时间差很大。有什么想法或建议吗?

我选择这种方法作为动态绑定列并形成查询。有没有其他更好的方法可以在 SQl Server 2008 中组织分页查询。

有没有办法可以提高查询的性能?

谢谢

4

7 回答 7

4

我总是检查我在查询中访问了多少数据,并尝试消除不必要的列和行。好吧,这些只是您可能已经检查过的明显点,但只是想指出以防万一。在您的查询中,性能缓慢可能是因为您执行“Select *”。从表中选择所有列不允许有好的执行计划。检查您是否只需要选定的列,并确保您在表 Orders 上有正确的覆盖索引。

因为在 SQL 2008 版本中没有显式的 SKIPP 或 OFFSET 函数,我们需要创建一个,我们可以通过 INNER JOIN 创建。在一个查询中,我们将首先使用 OrderDate 生成 ID,该查询中将没有其他内容。我们在第二个查询中执行相同的操作,但如果您需要 ALL 列,我们还会从表 ORDER 或 ALL 中选择一些其他感兴趣的列。然后我们加入它以按 ID 和 OrderDate 查询结果,并为第一个查询添加 SKIPP 行过滤器,其中数据集处于所需的最小大小。试试这个代码。

    SELECT q2.*
    FROM
    (
        SELECT ROW_NUMBER() OVER ( ORDER BY OrderDate ) AS RowNum, OrderDate
        FROM      Orders
        WHERE     OrderDate >= '1980-01-01'
    )q1
    INNER JOIN 
    (
        SELECT ROW_NUMBER() OVER ( ORDER BY OrderDate ) AS RowNum, *
        FROM      Orders
        WHERE     OrderDate >= '1980-01-01'
    )q2
        ON q1.RowNum=q2.RowNum AND q1.OrderDate=q2.OrderDate AND q1.rownum BETWEEN 30000 AND 30020

为了给你估计,我用以下测试数据尝试了这个,无论你查询哪个窗口,结果都会在不到 2 秒的时间内返回,并注意该表是 HEAP(无索引)表总共有 2M 行。测试选择正在查询从 50,000 到 50,010 的 10 行

下面的插入大约需要 8 分钟。

    IF object_id('TestSelect','u') IS NOT NULL
        DROP TABLE TestSelect
    GO
    CREATE TABLE TestSelect
    (
        OrderDate   DATETIME2(2)
    )
    GO

    DECLARE @i bigint=1, @dt DATETIME2(2)='01/01/1700'
    WHILE @I<=2000000
    BEGIN

        IF @i%15 = 0
            SELECT @DT = DATEADD(DAY,1,@dt)

        INSERT INTO dbo.TestSelect( OrderDate )
        SELECT @dt

        SELECT @i=@i+1
    END

选择窗口 50,000 到 50,010 用时不到 3 秒。

选择最后一行 2,000,000 到 2,000,000 也需要 3 秒。

    SELECT q2.*
    FROM
    (
        SELECT  ROW_NUMBER() OVER ( ORDER BY OrderDate ) AS RowNum 
                ,OrderDate
        FROM TestSelect
        WHERE OrderDate >= '1700-01-01'
    )q1
    INNER JOIN
    (
        SELECT  ROW_NUMBER() OVER ( ORDER BY OrderDate ) AS RowNum 
                ,*
        FROM TestSelect
        WHERE OrderDate >= '1700-01-01'
    )q2
        ON q1.RowNum=q2.RowNum 
        AND q1.OrderDate=q2.OrderDate 
        AND q1.RowNum BETWEEN 50000 AND 50010

在此处输入图像描述

于 2013-10-03T18:57:47.243 回答
2

ROW_NUMBER随着操作成本的广泛增长,这是一种糟糕的分页方式。

相反,您应该使用双子ORDER BY句。

假设您想使用ROW_NUMBER between 1200 and 1210. 而不是使用ROW_NUMBER() OVER (...)并稍后绑定结果,WHERE您应该:

SELECT TOP(11) *
FROM (
    SELECT TOP(1210) *
    FROM [...]
    ORDER BY something ASC
) subQuery
ORDER BY something DESC.

请注意,此查询将以相反的顺序给出结果。这不应该 - 一般而言 - 是一个问题,因为它很容易在 UI 中反转集合,例如 C#,特别是因为结果集合应该相对较小。

后者通常要快得多。请注意,后一种解决方案将通过CREATE CLUSTERED INDEX ...对用于对查询进行排序的列进行 CLUSTERING ( ) 来大大改进。

希望有帮助。

于 2013-10-10T08:16:55.243 回答
1

令人难以置信的是,没有其他答案提到在所有 SQL Server 版本中进行分页的最快方法,特别是关于 OP 的问题,即大页码的偏移量可能非常慢,如基准here

在 SQL 中执行分页有一种完全不同的、更快的方法。这通常被称为“seek 方法”,如本博文中所述。

SELECT TOP 10 *
FROM Orders
WHERE OrderDate >= '1980-01-01'
AND ((OrderDate > @previousOrderDate)
  OR (OrderDate = @previousOrderDate AND OrderId > @previousOrderId))
ORDER BY OrderDate ASC, OrderId ASC

@previousOrderDate@previousOrderId值是上一页最后一条记录的相应值。这允许您获取“下一页”。如果ORDER BY方向是DESC,只需使用即可<

使用上述方法,您不能在没有先获取前 40 条记录的情况下立即跳转到第 4 页。但通常,无论如何你都不想跳那么远。相反,您将获得更快的查询,该查询可能能够在恒定时间内获取数据,具体取决于您的索引。另外,您的页面保持“稳定”,无论基础数据是否发生变化(例如,在第 1 页,而您在第 4 页)。

例如,当在 Web 应用程序中延迟加载更多数据时,这是实现分页的最佳方式。

注意,“seek 方法”也称为keyset paging

于 2013-10-26T18:07:07.257 回答
1

即使您始终选择相同数量的行,当您想要在数据窗口末尾选择行时,性能也会下降。为了获取前 10 行,引擎只获取 10 行;要获得下一个 10,它必须获取 20,丢弃前 10,然后返回 10。要获得 30000 - 30010,它必须读取所有 30010,跳过前 30k,然后返回 10。

一些提高性能的技巧(不是完整列表,完全跳过了构建 OLAP)。您提到了联接;如果这可能加入不是内部查询,而是它的结果。您还可以尝试添加一些逻辑ORDER BY OrderDate- ASCDESC取决于您正在检索的存储桶。说如果你想抢“最后”10个, ORDER BY ... DESC会工作得更快。不用说,它必须是一个索引orderDate

于 2013-10-01T14:32:46.367 回答
0

查询的主要缺点是它对整个表进行排序并为每个查询计算 Row_Number。您可以通过在排序阶段使用更少的列(例如 Anup Shah 的建议)来简化 SQL Server 的工作。但是,您仍然可以读取、排序和计算每个查询的行号。

动态计算的替代方法是读取之前计算的值。

根据数据集的波动性以及用于排序和过滤的列数,您可以考虑:

  1. 添加一个 rownumber 列(或 2-3 列)并将其作为第一列包含在聚集索引中或创建非聚集索引)。

  2. 为最常见的组合创建视图,然后索引这些视图。它被称为索引(物化)视图。

这将允许读取行号,性能几乎不取决于卷。虽然维护这些将,但少于为每个查询排序整个表。

请注意,这是一次性查询并且与所有其他查询相比很少运行,最好只坚持查询优化:创建额外列/视图的努力可能不会得到回报。

于 2013-10-10T14:39:42.957 回答
0

@ peru,关于是否有更好的方法以及基于@ a1ex07提供的解释,请尝试以下操作 -

如果表具有唯一标识符,例如可以执行比较(大于、小于)操作的数字(订单 ID)或(订单日期,订单索引),则将其用作偏移量的行号。

例如,如果表 orders 有 'order_id' 作为主键,那么 -
要获得前十个结果 -
1。

select RowNum, order_id from   
( select 
ROW_NUMBER() OVER ( ORDER BY OrderDate ) AS RowNum, 
o.order_id 
from orders o where  o.order_id > 0 ;  
) 
tmp_qry where RowNum between 1 and 10 order by RowNum; // first 10 

假设最后返回的 order-id 是 17,

要选择下一个 10、2

select RowNum, order_id from   
( select 
ROW_NUMBER() OVER ( ORDER BY OrderDate ) AS RowNum, 
o.order_id 
from orders o where  o.order_id > 17 ;  
) 
tmp_qry where RowNum between 1 and 10 order by RowNum; // next 10 

请注意,row-num 值没有更改。其正在比较的 order-id 值已更改。

如果不存在这样的密钥,请考虑添加一个!

于 2013-10-09T18:48:04.377 回答
0
declare @pageOffset int
declare @pageSize int
-- set variables at some point

declare @startRow int
set @startRow = @pageOffset * @pageSize

declare @endRow int
set @endRow + @pageSize - 1

SELECT 
    o.*
FROM
(
    SELECT 
        ROW_NUMBER() OVER ( ORDER BY OrderDate ) AS RowNum
        , OrderId
    FROM      
        Orders
    WHERE     
        OrderDate >= '1980-01-01'
) q1
INNER JOIN Orders o
    on q1.OrderId = o.OrderId
where
    q1.RowNum between @startRow and @endRow
order by
    o.OrderDate
于 2013-10-05T04:17:40.373 回答