Indeks nie przyspiesza wykonywania, aw niektórych przypadkach spowalnia zapytanie. Dlaczego tak jest

34

Eksperymentowałem z indeksami, aby przyspieszyć działanie, ale w przypadku łączenia indeks nie poprawia czasu wykonywania zapytania, a w niektórych przypadkach spowalnia.

Zapytanie dotyczące utworzenia tabeli testowej i wypełnienia jej danymi to:

CREATE TABLE [dbo].[IndexTestTable](
    [id] [int] IDENTITY(1,1) PRIMARY KEY,
    [Name] [nvarchar](20) NULL,
    [val1] [bigint] NULL,
    [val2] [bigint] NULL)

DECLARE @counter INT;
SET @counter = 1;

WHILE @counter < 500000
BEGIN
    INSERT INTO IndexTestTable
      (
        -- id -- this column value is auto-generated
        NAME,
        val1,
        val2
      )
    VALUES
      (
        'Name' + CAST((@counter % 100) AS NVARCHAR),
        RAND() * 10000,
        RAND() * 20000
      );

    SET @counter = @counter + 1;
END

-- Index in question
CREATE NONCLUSTERED INDEX [IndexA] ON [dbo].[IndexTestTable]
(
    [Name] ASC
)
INCLUDE (   [id],
    [val1],
    [val2])

Teraz zapytanie 1, które jest ulepszone (tylko nieznacznie, ale poprawa jest spójna) to:

SELECT *
FROM   IndexTestTable I1
       JOIN IndexTestTable I2
            ON  I1.ID = I2.ID
WHERE  I1.Name = 'Name1'

Statystyki i plan wykonania bez indeksu (w tym przypadku tabela używa domyślnego indeksu klastrowego):

(5000 row(s) affected)
Table 'IndexTestTable'. Scan count 2, logical reads 5580, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 109 ms,  elapsed time = 294 ms.

wprowadź opis zdjęcia tutaj

Teraz z włączonym indeksem:

(5000 row(s) affected)
Table 'IndexTestTable'. Scan count 2, logical reads 2819, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 94 ms,  elapsed time = 231 ms.

wprowadź opis zdjęcia tutaj

Teraz zapytanie, które spowalnia z powodu indeksu (zapytanie jest bez znaczenia, ponieważ jest tworzone tylko do testowania):

SELECT I1.Name,
       SUM(I1.val1),
       SUM(I1.val2),
       MIN(I2.Name),
       SUM(I2.val1),
       SUM(I2.val2)
FROM   IndexTestTable I1
       JOIN IndexTestTable I2
            ON  I1.Name = I2.Name
WHERE   
       I2.Name = 'Name1'
GROUP BY
       I1.Name

Przy włączonym indeksie klastrowym:

(1 row(s) affected)
Table 'IndexTestTable'. Scan count 4, logical reads 60, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 1, logical reads 155106, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 17207 ms,  elapsed time = 17337 ms.

wprowadź opis zdjęcia tutaj

Teraz z wyłączonym indeksem:

(1 row(s) affected)
Table 'IndexTestTable'. Scan count 5, logical reads 8642, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 2, logical reads 165212, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 17691 ms,  elapsed time = 9073 ms.

wprowadź opis zdjęcia tutaj

Pytania są następujące:

  1. Mimo że indeks jest sugerowany przez SQL Server, dlaczego spowalnia rzeczy z powodu znacznej różnicy?
  2. Czym jest łączenie zagnieżdżonej pętli, które zajmuje najwięcej czasu i jak poprawić czas jej wykonywania?
  3. Czy jest coś, co robię źle lub które przeoczyłem?
  4. Przy domyślnym indeksie (tylko na kluczu podstawowym), dlaczego zajmuje to mniej czasu, a przy obecnym indeksie nieklastrowanym, dla każdego wiersza w tabeli łączącej wiersz tabeli połączonej należy znaleźć szybciej, ponieważ łączenie znajduje się w kolumnie Nazwa, w której indeks został utworzony. Znajduje to odzwierciedlenie w planie wykonania zapytania, a koszt Wyszukiwania Indeksu jest mniejszy, gdy IndexA jest aktywny, ale dlaczego nadal wolniejszy? Co także w lewym złączeniu zagnieżdżonym, które powoduje spowolnienie?

Korzystanie z SQL Server 2012

SpeedBirdNine
źródło

Odpowiedzi:

23

Mimo że indeks jest sugerowany przez SQL Server, dlaczego spowalnia rzeczy z powodu znacznej różnicy?

Sugestie dotyczące indeksu są tworzone przez optymalizator zapytań. Jeśli natrafi na logiczną selekcję z tabeli, która nie jest dobrze obsługiwana przez istniejący indeks, może dodać do swojego wyniku sugestię „brakującego indeksu”. Te sugestie są oportunistyczne; nie są oparte na pełnej analizie zapytania i nie uwzględniają szerszych rozważań. W najlepszym razie są one wskazówką, że może być możliwe bardziej pomocne indeksowanie, a wykwalifikowany DBA powinien się przyjrzeć.

Inną rzeczą do powiedzenia na temat brakujących sugestii indeksu jest to, że są one oparte na modelu kalkulacyjnym optymalizatora, a optymalizator szacuje, o ile sugerowany indeks może zmniejszyć szacowany koszt zapytania. Kluczowymi słowami są tutaj „model” i „szacunki”. Optymalizator zapytań niewiele wie o konfiguracji sprzętu lub innych opcjach konfiguracji systemu - jego model jest w dużej mierze oparty na stałych liczbach, które w większości przypadków zapewniają rozsądne wyniki planu większości ludzi w większości systemów. Oprócz problemów z dokładnymi zastosowanymi liczbami kosztów, wyniki są zawsze szacunkami - a szacunki mogą być błędne.

Czym jest łączenie zagnieżdżonej pętli, które zajmuje najwięcej czasu i jak poprawić czas jej wykonywania?

Niewiele można zrobić, aby poprawić wydajność samej operacji łączenia krzyżowego; zagnieżdżone pętle to jedyna możliwa fizyczna implementacja dla połączenia krzyżowego. Szpula stołu po wewnętrznej stronie złącza jest optymalizacją, aby uniknąć ponownego skanowania wewnętrznej strony dla każdego zewnętrznego rzędu. To, czy jest to przydatna optymalizacja wydajności, zależy od różnych czynników, ale w moich testach lepiej byłoby bez tego zapytania. Ponownie jest to konsekwencja zastosowania modelu kosztowego - mój procesor i system pamięci prawdopodobnie mają inne parametry wydajności niż twój. Nie ma konkretnej wskazówki dotyczącej zapytania, aby uniknąć buforowania tabeli, ale istnieje nieudokumentowana flaga śledzenia (8690), której można użyć do przetestowania wydajności wykonania z buforem i bez. Gdyby to był prawdziwy problem z systemem produkcyjnym, plan bez szpuli można by wymusić za pomocą przewodnika po planach opracowanego z włączonym TF 8690. Stosowanie nieudokumentowanych flag śledzenia w produkcji nie jest zalecane, ponieważ instalacja staje się technicznie nieobsługiwana, a flagi śledzenia mogą mieć niepożądane skutki uboczne.

Czy jest coś, co robię źle lub które przeoczyłem?

Główną rzeczą, której brakuje, jest to, że chociaż plan wykorzystujący indeks nieklastrowany ma niższy szacowany koszt zgodnie z modelem optymalizatora, ma znaczny problem z czasem wykonania. Jeśli spojrzysz na rozkład wierszy między wątkami w planie za pomocą Indeksu klastrowego, prawdopodobnie zobaczysz dość dobry rozkład:

Plan skanowania

W planie wykorzystującym wyszukiwanie nieklastrowane indeks praca jest wykonywana w całości przez jeden wątek:

Szukaj planu

Jest to konsekwencja sposobu, w jaki praca jest rozdzielana między wątki przez równoległe operacje skanowania / wyszukiwania. Nie zawsze jest tak, że skanowanie równoległe będzie lepiej rozprowadzać pracę niż wyszukiwanie indeksu - ale w tym przypadku tak jest. Bardziej złożone plany mogą obejmować wymianę partycjonowania w celu redystrybucji pracy między wątkami. W tym planie nie ma takich wymian, więc po przypisaniu wierszy do wątku wszystkie powiązane prace są wykonywane na tym samym wątku. Jeśli spojrzysz na rozkład pracy dla innych operatorów w planie wykonania, zobaczysz, że cała praca jest wykonywana przez ten sam wątek, jak pokazano dla wyszukiwania indeksu.

Brak wskazówek dotyczących zapytań, które mogłyby wpłynąć na rozkład wierszy między wątkami, ważne jest, aby zdawać sobie sprawę z możliwości i być w stanie przeczytać wystarczająco dużo szczegółów w planie wykonania, aby określić, kiedy powoduje problem.

Przy domyślnym indeksie (tylko na kluczu podstawowym), dlaczego zajmuje to mniej czasu, a przy obecnym indeksie nieklastrowanym, dla każdego wiersza w tabeli łączącej wiersz tabeli połączonej należy znaleźć szybciej, ponieważ łączenie znajduje się w kolumnie Nazwa, w której indeks został utworzony. Znajduje to odzwierciedlenie w planie wykonania zapytania, a koszt Wyszukiwania Indeksu jest mniejszy, gdy IndexA jest aktywny, ale dlaczego nadal wolniejszy? Co także w lewym złączeniu zagnieżdżonym, które powoduje spowolnienie?

Teraz powinno być jasne, że plan indeksu nieklastrowanego jest potencjalnie bardziej wydajny, jak można się spodziewać; tylko słaba dystrybucja pracy w wątkach w czasie wykonywania odpowiada za problem z wydajnością.

W celu uzupełnienia przykładu i zilustrowania niektórych rzeczy, o których wspomniałem, jednym ze sposobów na lepszą dystrybucję pracy jest użycie tabeli tymczasowej do sterowania równoległym wykonywaniem:

SELECT
    val1,
    val2
INTO #Temp
FROM dbo.IndexTestTable AS ITT
WHERE Name = N'Name1';

SELECT 
    N'Name1',
    SUM(T.val1),
    SUM(T.val2),
    MIN(I2.Name),
    SUM(I2.val1),
    SUM(I2.val2)
FROM   #Temp AS T
CROSS JOIN IndexTestTable I2
WHERE
    I2.Name = 'Name1'
OPTION (FORCE ORDER, QUERYTRACEON 8690);

DROP TABLE #Temp;

Wynikiem tego jest plan, który wykorzystuje bardziej wydajne wyszukiwanie indeksów, nie zawiera buforu tabeli i dobrze rozdziela pracę między wątkami:

Optymalny plan

W moim systemie ten plan działa znacznie szybciej niż wersja Clustered Index Scan.

Jeśli chcesz dowiedzieć się więcej o wewnętrznych funkcjach równoległego wykonywania zapytań, możesz obejrzeć moje nagranie sesji PASS Summit 2013 .

Paul White mówi GoFundMonica
źródło
0

To nie jest tak naprawdę kwestia indeksu, to raczej źle napisane zapytanie. Masz tylko 100 unikalnych wartości nazwy, co pozostawia unikalną liczbę 5000 na nazwę.

Tak więc dla każdej linii w tabeli 1 łączysz 5000 z tabeli 2. Czy możesz powiedzieć 25020004 linii.

Spróbuj tego, pamiętaj, że jest to tylko jeden indeks, ten, który wymieniłeś.

    DECLARE @Distincts INT
    SET @Distincts = (SELECT  TOP 1 COUNT(*) FROM IndexTestTable I1 WHERE I1.Name = 'Name1' GROUP BY I1.Name)
    SELECT I1.Name
    , @Distincts
    , SUM(I1.val1) * @Distincts
    , SUM(I1.val2) * @Distincts
    , MIN(I2.Name)
    , SUM(I2.val1)
    , SUM(I2.val2)
    FROM   IndexTestTable I1
    LEFT OUTER JOIN

    (
        SELECT I2.Name
        , SUM(I2.val1) val1
        , SUM(I2.val2) val2
        FROM IndexTestTable I2
        GROUP BY I2.Name
    ) I2 ON  I1.Name = I2.Name
    WHERE I1.Name = 'Name1'
    GROUP BY  I1.Name

I czas:

    SQL Server parse and compile time: 
       CPU time = 0 ms, elapsed time = 8 ms.
    Table 'IndexTestTable'. Scan count 1, logical reads 31, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

     SQL Server Execution Times:
       CPU time = 0 ms,  elapsed time = 1 ms.

    (1 row(s) affected)
    Table 'IndexTestTable'. Scan count 2, logical reads 62, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

     SQL Server Execution Times:
       CPU time = 16 ms,  elapsed time = 10 ms.

wprowadź opis zdjęcia tutaj

Nie można obwiniać indeksów SQL za źle utworzone zapytania

Adrian Sullivan
źródło
1
Dzięki za odpowiedź i tak, zapytanie można poprawić, ale logika mojego pytania polegała na tym, że przy domyślnym indeksie (tylko na kluczu podstawowym) dlaczego zajmuje to mniej czasu i przy obecnym indeksie nieklastrowanym dla każdego wiersza w tabela łącząca, wiersz połączonej tabeli powinien zostać znaleziony szybciej, co znajduje odzwierciedlenie w planie wykonania zapytania, a koszt Wyszukiwania Indeksu jest mniejszy, gdy Indeks A jest aktywny, ale dlaczego nadal wolniejszy? Co także w lewym złączeniu zagnieżdżonym, które powoduje spowolnienie? Zredagowałem pytanie, aby dodać ten komentarz, aby wyjaśnić pytanie.