Połączyć zasięg szukać na zerowym indeksie kompozytowym?

14

Dla następującego schematu i przykładowych danych

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 

Aplikacja przetwarza wiersze z tej tabeli w kolejności indeksów klastrowych w 1000 porcjach wiersza.

Pierwsze 1000 wierszy jest pobieranych z następującego zapytania.

SELECT TOP 1000 *
FROM   T
ORDER  BY A, B 

Ostatni rząd tego zestawu znajduje się poniżej

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

Czy jest jakiś sposób na napisanie zapytania, które szuka tylko tego złożonego klucza indeksu, a następnie podąża za nim, aby pobrać następną porcję 1000 wierszy?

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

Najniższa liczba odczytów, jaką udało mi się dotychczas uzyskać, to 1020, ale zapytanie wydaje się zdecydowanie zbyt skomplikowane. Czy istnieje prostszy sposób na zapewnienie równej lub lepszej wydajności? Być może taki, któremu uda się to wszystko zrobić w jednym zasięgu?

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 

wprowadź opis zdjęcia tutaj


FWIW: Jeśli kolumna Azostanie utworzona, NOT NULLa -1zamiast niej zostanie użyta wartość wartownika, równoważny plan wykonania z pewnością wygląda na prostszy

wprowadź opis zdjęcia tutaj

Ale operator wyszukiwania pojedynczego w planie nadal wykonuje dwa poszukiwania, zamiast łączyć je w jeden ciągły zakres, a logiczne odczyty są w przybliżeniu takie same, więc podejrzewam, że być może jest to tak dobre, jak to możliwe?

Martin Smith
źródło
Mój błąd. Zapomniałem, że NULLwartości są zawsze pierwsze. (zakładał się odwrotnie.) Poprawiony stan w Fiddle
ypercubeᵀᴹ
Tak, Oracle jest inny, wierzę.
Martin Smith,
SQL Fiddle
Martin Smith
@ypercube - SQL Server po prostu niestety zleca skanowanie, dlatego ponownie odczytuje wszystkie wiersze już przetworzone przez aplikację (logiczne odczyty 2015). Nie dąży do pierwszego klucza(NULL, 1000 )
Martina Smitha
Przy 2 różnych warunkach, niezależnie od tego, czy @Ama wartość zerową, czy nie, wydaje się, że nie wykonuje skanowania. Ale nie rozumiem, czy plany są lepsze niż twoje zapytanie. Fiddle-2
ypercubeᵀᴹ

Odpowiedzi:

21

Czy jest jakiś sposób na napisanie zapytania, które szuka tylko tego złożonego klucza indeksu, a następnie podąża za nim, aby pobrać następną porcję 1000 wierszy?

Moim ulubionym rozwiązaniem jest użycie APIkursora:

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;

Ogólna strategia to pojedynczy skan, który zapamiętuje swoją pozycję między połączeniami. Używanie APIkursora oznacza, że ​​możemy zwracać blok wierszy zamiast jednego na raz, tak jak w przypadku T-SQLkursora:

Plany wykonania

Dane STATISTICS IOwyjściowe to:

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 9
źródło