对于以下架构和示例数据
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
Run Code Online (Sandbox Code Playgroud)
应用程序正在以 1,000 个行块的聚集索引顺序处理该表中的行。
从以下查询中检索前 1,000 行。
SELECT TOP 1000 *
FROM T
ORDER BY A, B
Run Code Online (Sandbox Code Playgroud)
该组的最后一行在下面
+------+------+
| A | B |
+------+------+
| NULL | 1000 |
+------+------+
Run Code Online (Sandbox Code Playgroud)
有什么方法可以编写一个查询,该查询只查找该复合索引键,然后跟随它来检索下一个 1000 行块?
/*Pseudo Syntax*/
SELECT TOP 1000 *
FROM T
WHERE (A, B) is_ordered_after (@A, @B)
ORDER BY A, B
Run Code Online (Sandbox Code Playgroud)
到目前为止,我设法获得的最低读取次数是 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
Run Code Online (Sandbox Code Playgroud)

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

但是计划中的单个搜索运算符仍然执行两次搜索而不是将其折叠到一个连续的范围内,并且逻辑读取大致相同,所以我怀疑这可能与它所获得的一样好?
Pau*_*ite 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;
Run Code Online (Sandbox Code Playgroud)
总体策略是一次扫描,记住它在调用之间的位置。使用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
Run Code Online (Sandbox Code Playgroud)