在可空的复合索引上重新加入范围搜索?


14

对于以下架构和示例数据

CREATE TABLE T
  (
     A INT NULL,
     B INT NOT NULL IDENTITY,
     C CHAR(8000) NULL,
     UNIQUE CLUSTERED (A, B)
  )

INSERT INTO T
            (A)
SELECT NULLIF(( ( ROW_NUMBER() OVER (ORDER BY @@SPID) - 1 ) / 1003 ), 0)
FROM   master..spt_values 

应用程序正在以1,000个行大块的聚集索引顺序处理该表中的行。

从以下查询中检索前1,000行。

SELECT TOP 1000 *
FROM   T
ORDER  BY A, B 

该集合的最后一行在下面

+------+------+
|  A   |  B   |
+------+------+
| NULL | 1000 |
+------+------+

有什么方法可以编写只查询该复合索引键,然后跟着它检索下一个1000行数据块的查询?

/*Pseudo Syntax*/
SELECT TOP 1000 *
FROM   T
WHERE (A, B) is_ordered_after (@A, @B)
ORDER  BY A, B 

到目前为止,我设法获得的读取次数最少的是1020,但是查询似乎太复杂了。有没有更简单的平等或更高效率的方法?也许一个人设法在一个范围内做到这一切?

DECLARE @A INT = NULL, @B INT = 1000

;WITH UnProcessed
     AS (SELECT *
         FROM   T
         WHERE  ( EXISTS(SELECT A
                         INTERSECT
                         SELECT @A)
                  AND B > @B )
         UNION ALL
         SELECT *
         FROM   T
         WHERE @A IS NULL AND A IS NOT NULL
         UNION ALL
         SELECT *
         FROM   T
         WHERE A > @A        
         )
SELECT TOP 1000 *
FROM   UnProcessed
ORDER  BY A,
          B 

在此处输入图片说明


FWIW:如果列A是由NOT NULL和警戒值-1来代替相当于执行计划肯定看起来简单

在此处输入图片说明

但是,计划中的单个搜索运算符仍然执行两次搜索,而不是将其折叠到一个连续的范围内,并且逻辑读取值几乎相同,因此我怀疑这可能和它获得的效果一样好吗?


我的错。我忘记了NULL价值观永远是第一位的。(假设相反)。在Fiddle中
–ypercubeᵀᴹ14年

是的,我相信Oracle是与众不同的。
马丁·史密斯


@ypercube-不幸的是,SQL Server仅为该事件提供了有序扫描,因此重新读取了该应用程序已处理的所有行(逻辑读取为2015)。它没有(NULL, 1000 )
马丁史密斯

在2种不同的条件下,无论是否@A为null,似乎都不会进行扫描。但是我不明白这些计划是否比您的查询更好。小提琴2
ypercubeᵀᴹ

Answers:


21

有什么方法可以编写只查询该复合索引键,然后跟着它检索下一个1000行数据块的查询?

我最喜欢的解决方案是使用API游标:

SET NOCOUNT ON;
SET STATISTICS IO ON;

DECLARE 
    @cur integer,
    -- FAST_FORWARD, AUTO_FETCH, AUTO_CLOSE, CHECK_ACCEPTED_TYPES, FAST_FORWARD_ACCEPTABLE
    @scrollopt integer = 16 | 8192 | 16384 | 32768 | 1048576,
    -- READ_ONLY, CHECK_ACCEPTED_OPTS, READ_ONLY_ACCEPTABLE
    @ccopt integer = 1 | 32768 | 65536, 
    @rowcount integer = 1000,
    @rc integer;

-- Open the cursor and return (up to) the first 1000 rows
EXECUTE @rc = sys.sp_cursoropen
    @cur OUTPUT,
    N'
    SELECT A, B, C
    FROM T
    ORDER BY A, B;
    ',
    @scrollopt OUTPUT,
    @ccopt OUTPUT,
    @rowcount OUTPUT;

IF @rc <> 16 -- FastForward cursor automatically closed
BEGIN
    -- Name the cursor so we can use CURSOR_STATUS
    EXECUTE sys.sp_cursoroption
        @cur, 
        2, 
        'MyCursorName';

    -- Until the cursor auto-closes
    WHILE CURSOR_STATUS('global', 'MyCursorName') = 1
    BEGIN
        EXECUTE sys.sp_cursorfetch
            @cur,
            2,
            0,
            1000;
    END;
END;

SET STATISTICS IO OFF;

整体策略是一次扫描,可以记住两次呼叫之间的位置。使用API游标意味着我们可以一次返回一行行块,而不是一次返回一个行块T-SQL

执行计划

STATISTICS IO输出是:

Table 'T'. Scan count 1, logical reads 1011, physical reads 0, read-ahead reads 0
Table 'T'. Scan count 1, logical reads 1001, physical reads 0, read-ahead reads 0
Table 'T'. Scan count 1, logical reads 516, physical reads 0, read-ahead reads 0
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.