Kiedy najlepiej zamieścić pytanie w tagu sql-server na StackOverflow? Część II

Wolnoooooo

Jak pamiętacie z poprzedniego wpisu, “Kiedy najlepiej zamieścić pytanie w tagu sql-server na StackOverflow?“, najlepiej pytania jest zamieszczać w sobotę między 02:15:00 a 02:29:59 😉 I pamiętacie zapewne, że zapytanie tam podane wykonywało się u mnie ponad 2 minuty. Za wolno, trzeba to przyspieszyć! Poniżej przypomnienie kwerendy i lecimy.

;WITH RawData AS (
SELECT
    p.[Id] as [PostId],
    DATEDIFF(SECOND,p.[CreationDate],pFA.[CreationDate]) as [TimeElapsedInSeconds],
    DATEPART(WEEKDAY,p.[CreationDate]) as [WeekdayOfQuestion],
    CAST(CASE
        WHEN DATEPART(MINUTE,p.[CreationDate]) < 15 THEN
            TIMEFROMPARTS(DATEPART(HOUR,p.[CreationDate]),0,0,0,0)
        WHEN DATEPART(MINUTE,p.[CreationDate]) BETWEEN 15 AND 29 THEN
            TIMEFROMPARTS(DATEPART(HOUR,p.[CreationDate]),15,0,0,0)
        WHEN DATEPART(MINUTE,p.[CreationDate]) BETWEEN 30 AND 44 THEN
            TIMEFROMPARTS(DATEPART(HOUR,p.[CreationDate]),30,0,0,0)
        ELSE
            TIMEFROMPARTS(DATEPART(HOUR,p.[CreationDate]),45,0,0,0)
    END AS TIME(0)) as [TimeOfQuestion]
FROM
    [dbo].[Posts] p
CROSS APPLY
    (SELECT TOP(1)
        [Id],
        [CreationDate]
    FROM
        [dbo].[Posts] pA
    WHERE
        p.[Id] = pA.[ParentId]
    ORDER BY
        pA.[CreationDate]) pFA
WHERE
   p.[Tags] LIKE '%<sql-server>%'
)
SELECT DISTINCT
    [WeekdayOfQuestion],
    [TimeOfQuestion],
    PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY [TimeElapsedInSeconds]) OVER (PARTITION BY [WeekdayOfQuestion],[TimeOfQuestion]) AS [Median]
FROM
    [RawData]
ORDER BY
    [Median];

Rzut oka na plan

Mamy zapytanie, wykonało się, zerknijmy na plan zapytania. Od razu widać, że największe rzeczy dzieją się na końcu, czyli tak naprawdę na początku, bo plany wykonują się od prawej do lewej, chociaż tak naprawdę to od lewej do prawej, więc… pogmatwana kwestia, kiedyś napiszę dlaczego oba punkty widzenia są poprawne.

Na razie nie skupimy się na tym dużym operatorze Index Scan u góry, tylko na gałęzi poniżej, bo posiada ona pewien podchwytliwy operator, który bardzo nam psuje całe zapytanie.

Jaeger… Eager Index Spool

Index Spool (Eager Spool) jest operatorem blokującym i upierdliwym. Po pierwsze, oznacza on, że SQL Server postanowił zbudować samemu indeks, nic nam o tym nie mówiąc (nie ma też podpowiedzi na zielono na górze planu), wykorzystać go w wykonaniu zapytania, a potem porzucić jak marzenia o wakacjach w 2020. Po drugie, jest on typu Eager, czyli dopóki nie otrzyma wszystkich wierszy z poprzedzającego operatora (Clustered Index Scan) nie puści pracy dalej, więc cała robota stoi w miejscu i następny po nim Sort nawet nie może się zacząć wykonywać (stąd określenie “blokujący”).

I po TRZECIE

Widzicie te czarne strzałki na pomarańczowych kółkach przy operatorach? To oznacza, że dany element planu został wykonany równolegle, czyli robota została podzielona na kilka/kilkanaście gałęzi i każda wykonywała się niezależnie. Ale czy na pewno?

Jeżeli spojrzymy na obrazek powyżej, to właściwości operatora (wyświetlić je można zaznaczając operator i wciskając F4 w SSMS) mówią nam, że rzeczywiście, ten operator był wykonany równolegle. Yay!

NIE!

Nigdy nie ufajcie ikonkom.

Gdy zajrzymy w rozkład wierszy per wątek… Wygląda to jak typowy projekt na studiach. 8 osób w grupie, jedna robi wszystko, reszta się obija ale pod wynikiem podpisują się wszyscy. Eager Index Spool wymusza na poprzedzającym operatorze pracę jednowątkową! Koniec, kropka, nie ma dyskusji, marsz do pokoju.

Farewell, Mr. Spool

Musimy się tego pozbyć. Jako, że jest to indeks tymczasowy to zapewne jesteśmy w stanie stworzyć taki indeks na stałe i wtedy zapytanie będzie z niego korzystać. Tylko jak się dowiedzieć jaki indeks został stworzony? Nie tak trudno, wystarczy najechać na operator i…

Aktualnie interesuje nas tylko fragment zaznaczony na czerwono, ale to nie oznacza, że nie ma tam jeszcze czegoś. Seek Predicate informuje nas o tym jakie kolumny wchodzą w skład klucza indeksu i do czego zostały użyte. Tutaj widzimy, że indeks został stworzony na kolumnie ParentId z tabeli Posts i posłużył do łączenia się z kolumną Id z tej samej tabeli (nasz warunek WHERE z CROSS APPLY). Na co czekać, lecimy tutaj!

CREATE INDEX [doggo] ON [dbo].[Posts]([ParentId]);

Piękny indeks na kolumnie ParentId na pewno nam pomoże. F5

Jest lepiej. Index Spool zniknoł, tzn. zaginoł na zawsze, pojawił się za to Index Seek połączony z Key Lookupem. Powróciła również równoległość, liczba wierszy została w miarę równo rozdzielona na wątki! Zapytanie wykonało się w ok. 65 sekund, więc duży zysk z ponad 2 minut, ale dalej chce się więcej. Można powiedzieć, że z zupełnego bagna przeszliśmy do etapu “ciulowo ale stabilnie”.

Key Lookup

Naszym następnym problemem jest ten nieszczęsny Key Lookup. Oznacza on nic innego jak to, że w naszym psim indeksie nie było wszystkich kolumn jakie operator potrzebował wyciągnąć, więc łączył się z indeksem klastrowym na tabeli w celu pobrania dodatkowych informacji. Jakich?

Przypomnijmy sobie nasz Index Spool. Nad wartością Seek Predicate była jeszcze jedna grupa. Output List. To nic innego jak lista kolumn, które indeks zwraca dodatkowo. Brakuje nam więc w nim CreationDate. Ale czy dodać to do klucza czy do klauzuli INCLUDE? Spójrzmy trochę w przód, mamy tam nieprzyjemny Sort (Top N Sort). Niby nic nie wadzi, ale fajnie jakby go nie było. Po czym sortujemy? Po niczym innym jak CreationDate, bo taki ORDER BY mamy w naszym CROSS APPLY. Czyli dodajemy tę kolumnę do klucza!

DROP INDEX [doggo] ON [dbo].[Posts];
CREATE INDEX [birb] ON [dbo].[Posts]([ParentId],[CreationDate]);

Ptasi indeks gotowy, F5!

Nie ma Key Lookup! Nie ma Sort! A zapytanie dalej wykonało się w ok. 65 sekund…

Do trzech razy sztuka!

Dotychczas unikaliśmy słonia w pokoju. Górnej gałęzi planu, która ma skan całej tabeli Posts i zajmuje ponad minutę. I tutaj pojawia się problem… Nasz warunek.

WHERE
   p.[Tags] LIKE '%<sql-server>%'

Klops, warunek zaczyna się od znaku % czyli indeks nam nie pomoże… Ale czy aby na pewno? Pamiętajmy, że SQL Server zawsze chce użyć najmniejszej kopii tabeli jaką ma dostępną i jaka posiada wszystkie wartości, które go interesują. Jednak umieszczenie kolumny Tags w kluczu indeksu nie będzie miało tutaj zbyt dużego sensu, bo i tak wykonujemy na niej operacje, więc silnik nie będzie w stanie przeskoczyć bezpośrednio do interesującej nas wartości, musi wykonać swój LIKE. Dlatego dodajmy to do klauzuli INCLUDE, która umieści naszą kolumnę tylko na poziomie liści indeksu i dzięki temu będziemy mieli najmniejszą kopię tabeli, która zawiera wszystkie dane potrzebne do wykonania naszego zapytania.

DROP INDEX [birb] ON [dbo].[Posts]
CREATE INDEX [kitten] ON [dbo].[Posts]([ParentId],[CreationDate]) INCLUDE ([Tags]);

Papa ptaszku, witaj kitku! F5

<3 Miłosć, nirvana, oranżada w proszku. Całe zapytanie wykonało się w 4 sekundy (!) i mimo, że dalej mamy Index Scan, jest to już skan naszego indeksu, czyli tylko małego wycinka całej tabeli. Czyli z 2 minut i 42 sekund zeszliśmy do 4 sekund. 162 vs 4. 40x szybciej.

Oki, to pa!

Zobacz także:

  • Piotr Szymański

    Kategoria:

    Hejka naklejka! Google przeprowadza restrukturyzację swojego zespołu finansowego, kładąc większy nacisk na inwestycje w sztuczną inteligencję. Zmiany, o których poinformowała główna dyrektor finansowa Google, Ruth Porat, obejmują zwolnienia oraz przeniesienia pracowników i są odpowiedzią na spowolnienie wzrostu przychodów z reklam, jednocześnie stawiając na rosnącą rolę AI. CFO zaznaczyła, że restrukturyzacja jest częścią szerszej strategii przystosowania […]
  • Piotr Szymański

    Kategoria:

    Cześć! Zapraszam do podsumowania minionych 2 tygodni, wiadomości, które zaciekawiły mnie podczas surfowania po sieci telekomunikacyjnej Internet. Sekretarz Sił Powietrznych USA Frank Kendall ogłosił plany lotu myśliwcem F-16 sterowanym przez sztuczną inteligencję. Próba ma odbyć się wiosną tego roku i ma na celu zaprezentowanie potencjału sztucznej inteligencji w walce powietrznej, podkreślając przejście wojska w kierunku […]
  • Piotr Szymański

    Kategoria:

    Hejka naklejka! Świat jest podzielony. Jak zawsze, można by rzec. Wiecznie żywa reguła „dziel i rządź”, można by rzec. Świat podzielony na zwolenników sernika z rodzynkami, jak i bez rodzynków; wielbicieli sałatki jarzynowej jak i ludzi pozbawionych gustu; ludzi jeżdżących BMW, jak i masochistów, i tak dalej, i tym podobne. Przychodzę tu jednak ponad pewnymi […]
  • Piotr Szymański

    Kategoria:

    Siemson sportowe świry! Dumni po zwycięstwie, wierni po porażce. Tym razem jednak bez wstydu, hańby i kompromitacji. Nie podejrzewam naszego sztabu szkoleniowego o klasyczną inspirację rozwiązaniem stosowanym w Liverpoolu, natomiast nie miał bym nic przeciwko. We współpracy z Google DeepMind powstał bowiem TacticAI, model predykcyjny wspomagający wysiłki trenerów. Już w 2021 potrafił zgadnąć gdzie piłkarz […]
  • Piotr Szymański

    Kategoria:

    Hæ vinir! Dinozaury. Kto z nas nie byłby na pewnym etapie życia zafascynowany tymi uroczymi gadami. Nawet nie wiecie, ile Nas z nimi łączy! Nie tylko tak samo jesteśmy owodniowcami, ale i wyginiemy. Może nawet całkiem niedługo. Mrocznie się zrobiło, nie? Ale może zakończymy jakimś przyjemnym akcentem, więc bądźcie nastrojeni. Raport, stworzony przez Gladstone AI […]