Erneutes Aufsuchen des Bereichs auf einen nullbaren zusammengesetzten Index?

14

Für das folgende Schema und Beispieldaten

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 

Eine Anwendung verarbeitet die Zeilen aus dieser Tabelle in Clustered-Index-Reihenfolge in 1.000 Zeilenblöcken.

Die ersten 1.000 Zeilen werden aus der folgenden Abfrage abgerufen.

SELECT TOP 1000 *
FROM   T
ORDER  BY A, B 

Die letzte Reihe dieses Sets befindet sich unten

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

Gibt es eine Möglichkeit, eine Abfrage zu schreiben, die nur nach diesem zusammengesetzten Indexschlüssel sucht und ihm dann folgt, um den nächsten Teil von 1000 Zeilen abzurufen?

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

Die niedrigste Anzahl von Lesevorgängen, die ich bisher erreicht habe, ist 1020, aber die Abfrage scheint viel zu kompliziert zu sein. Gibt es einen einfacheren Weg zu gleicher oder besserer Effizienz? Vielleicht schafft es einer, alles in einem Bereich zu suchen?

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 

Bildbeschreibung hier eingeben


FWIW: Wenn die Spalte Aerstellt NOT NULLund -1stattdessen ein Sentinel-Wert von verwendet wird, sieht der entsprechende Ausführungsplan mit Sicherheit einfacher aus

Bildbeschreibung hier eingeben

Aber der einzelne Suchoperator im Plan führt immer noch zwei Suchvorgänge durch, anstatt ihn in einen zusammenhängenden Bereich zu reduzieren, und die logischen Lesevorgänge sind weitgehend gleich. Ich vermute also, dass dies so gut wie möglich ist.

Martin Smith
quelle
Mein Fehler. Ich habe vergessen, dass NULLWerte immer an erster Stelle stehen. (Gegenteil angenommen.) Korrigierter Zustand bei Fiddle
ypercubeper
Ja, Oracle ist anders, glaube ich.
Martin Smith
SQL Fiddle
Martin Smith
@ypercube - SQL Server gibt dafür leider nur einen geordneten Scan aus, sodass alle bereits von der Anwendung verarbeiteten Zeilen erneut gelesen werden (logische Lesevorgänge 2015). Es sucht nicht nach dem ersten Schlüssel von(NULL, 1000 )
Martin Smith
Bei 2 verschiedenen Bedingungen wird @Aanscheinend kein Scan durchgeführt , unabhängig davon, ob Null ist oder nicht. Aber ich kann nicht verstehen, ob die Pläne besser sind als Ihre Anfrage. Fiddle-2
ypercubeᵀᴹ

Antworten:

21

Gibt es eine Möglichkeit, eine Abfrage zu schreiben, die nur nach diesem zusammengesetzten Indexschlüssel sucht und ihm dann folgt, um den nächsten Teil von 1000 Zeilen abzurufen?

Eine meiner Lieblingslösungen ist die Verwendung eines APICursors:

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;

Die Gesamtstrategie ist ein einzelner Scan, bei dem die Position zwischen den Aufrufen gespeichert wird. Die Verwendung eines APICursors bedeutet, dass wir einen Zeilenblock zurückgeben können, anstatt wie bei einem T-SQLCursor einen nach dem anderen:

Ausführungspläne

Die STATISTICS IOAusgabe ist:

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
Paul White Monica wieder einsetzen
quelle