Sqlserver
 sql >> Baza danych >  >> RDS >> Sqlserver

podzapytanie lub leftjoin z grupą, dzięki której jest szybsze?

Świetnym źródłem informacji o obliczaniu bieżących sum w SQL Server jest ten dokument autorstwa Itzika Bena Gana, który został zgłoszony do zespołu SQL Server w ramach jego kampanii, aby uzyskać OVER klauzula rozszerzona w stosunku do początkowej implementacji SQL Server 2005. W nim pokazuje, jak po dostaniu się do dziesiątek tysięcy wierszy kursory wykonują rozwiązania oparte na zbiorach. SQL Server 2012 rzeczywiście rozszerzył OVER klauzula znacznie ułatwiająca tego rodzaju zapytania.

SELECT col1,
       SUM(col1) OVER (ORDER BY ind ROWS UNBOUNDED PRECEDING)
FROM   @tmp 

Ponieważ korzystasz z SQL Server 2005, nie jest to dla Ciebie dostępne.

Adam Machanic pokazuje tutaj jak CLR może być używany do poprawy wydajności standardowych kursorów TSQL.

Dla tej definicji tabeli

CREATE TABLE RunningTotals
(
ind int identity(1,1) primary key,
col1 int
)

Tworzę tabele z 2000 i 10 000 wierszy w bazie danych z ALLOW_SNAPSHOT_ISOLATION ON i jeden z tym ustawieniem (powodem tego jest to, że moje początkowe wyniki były w bazie danych z włączonym ustawieniem, które prowadziło do zagadkowego aspektu wyników).

Indeksy klastrowane dla wszystkich tabel miały tylko 1 stronę główną. Liczba stron liści dla każdej z nich jest pokazana poniżej.

+-------------------------------+-----------+------------+
|                               | 2,000 row | 10,000 row |
+-------------------------------+-----------+------------+
| ALLOW_SNAPSHOT_ISOLATION OFF  |         5 |         22 |
| ALLOW_SNAPSHOT_ISOLATION ON   |         8 |         39 |
+-------------------------------+-----------+------------+

Przetestowałem następujące przypadki (Linki pokazują plany wykonania)

  1. Dołącz od lewej i grupuj według
  2. Skorelowane podzapytanie plan 2000 wierszy ,plan 10000 wierszy
  3. CTE z odpowiedzi Mikaela (zaktualizowanej)
  4. CTE poniżej

Powodem włączenia dodatkowej opcji CTE było zapewnienie rozwiązania CTE, które nadal będzie działać, jeśli ind kolumna nie była gwarantowana sekwencyjnie.

SET STATISTICS IO ON;
SET STATISTICS TIME ON;
DECLARE @col1 int, @sumcol1 bigint;

WITH    RecursiveCTE
AS      (
        SELECT TOP 1 ind, col1, CAST(col1 AS BIGINT) AS Total
        FROM RunningTotals
        ORDER BY ind
        UNION   ALL
        SELECT  R.ind, R.col1, R.Total
        FROM    (
                SELECT  T.*,
                        T.col1 + Total AS Total,
                        rn = ROW_NUMBER() OVER (ORDER BY T.ind)
                FROM    RunningTotals T
                JOIN    RecursiveCTE R
                        ON  R.ind < T.ind
                ) R
        WHERE   R.rn = 1
        )
SELECT  @col1 =col1, @sumcol1=Total
FROM    RecursiveCTE
OPTION  (MAXRECURSION 0);

Wszystkie zapytania miały CAST(col1 AS BIGINT) dodano w celu uniknięcia błędów przepełnienia w czasie wykonywania. Dodatkowo dla wszystkich przypisałem wyniki do zmiennych jak powyżej, aby wyeliminować czas poświęcony na odesłanie wyników z rozpatrzenia.

Wyniki

+------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
|                  |          |        |          Base Table        |         Work Table         |     Time        |
+------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
|                  | Snapshot | Rows   | Scan count | logical reads | Scan count | logical reads | cpu   | elapsed |
| Group By         | On       | 2,000  | 2001       | 12709         |            |               | 1469  | 1250    |
|                  | On       | 10,000 | 10001      | 216678        |            |               | 30906 | 30963   |
|                  | Off      | 2,000  | 2001       | 9251          |            |               | 1140  | 1160    |
|                  | Off      | 10,000 | 10001      | 130089        |            |               | 29906 | 28306   |
+------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
| Sub Query        | On       | 2,000  | 2001       | 12709         |            |               | 844   | 823     |
|                  | On       | 10,000 | 2          | 82            | 10000      | 165025        | 24672 | 24535   |
|                  | Off      | 2,000  | 2001       | 9251          |            |               | 766   | 999     |
|                  | Off      | 10,000 | 2          | 48            | 10000      | 165025        | 25188 | 23880   |
+------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
| CTE No Gaps      | On       | 2,000  | 0          | 4002          | 2          | 12001         | 78    | 101     |
|                  | On       | 10,000 | 0          | 20002         | 2          | 60001         | 344   | 342     |
|                  | Off      | 2,000  | 0          | 4002          | 2          | 12001         | 62    | 253     |
|                  | Off      | 10,000 | 0          | 20002         | 2          | 60001         | 281   | 326     |
+------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
| CTE Alllows Gaps | On       | 2,000  | 2001       | 4009          | 2          | 12001         | 47    | 75      |
|                  | On       | 10,000 | 10001      | 20040         | 2          | 60001         | 312   | 413     |
|                  | Off      | 2,000  | 2001       | 4006          | 2          | 12001         | 94    | 90      |
|                  | Off      | 10,000 | 10001      | 20023         | 2          | 60001         | 313   | 349     |
+------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+

Zarówno skorelowane podzapytanie, jak i GROUP BY wersja używa "trójkątnych" sprzężeń zagnieżdżonych sterowanych przez skanowanie indeksu klastrowego w RunningTotals tabela (T1 ) i dla każdego wiersza zwróconego przez ten skan, szukanie z powrotem do tabeli (T2 ) samołączenie na T2.ind<=T1.ind .

Oznacza to, że te same wiersze są przetwarzane wielokrotnie. Gdy T1.ind=1000 wiersz jest przetwarzany, samo łączenie pobiera i sumuje wszystkie wiersze z ind <= 1000 , a następnie dla następnego wiersza, gdzie T1.ind=1001 te same 1000 wierszy jest pobieranych ponownie i zsumowane wraz z jednym dodatkowym wierszem i tak dalej.

Łączna liczba takich operacji dla tabeli zawierającej 2000 wierszy wynosi 2 001 000, dla 10 tys. wierszy 50 005 000 lub ogólniej (n² + n) / 2 który wyraźnie rośnie wykładniczo.

W przypadku 2000 wierszy główna różnica między GROUP BY a wersje podzapytania polegają na tym, że pierwsza z nich ma agregację strumienia po łączeniu, a więc ma trzy kolumny zasilające do niej (T1.ind , T2.col1 , T2.col1 ) i GROUP BY właściwość T1.ind podczas gdy ta ostatnia jest obliczana jako agregacja skalarna, z agregacją strumienia przed sprzężeniem, ma tylko T2.col1 karmienie do niego i nie ma GROUP BY zestaw właściwości. Ten prostszy układ daje wymierne korzyści w postaci skrócenia czasu procesora.

W przypadku 10 000 wierszy istnieje dodatkowa różnica w planie podrzędnych zapytań. Dodaje gorącą szpulę który kopiuje wszystkie ind,cast(col1 as bigint) wartości do tempdb . W przypadku, gdy izolacja migawek jest włączona, działa to bardziej kompaktowo niż struktura indeksu klastrowego, a efektem netto jest zmniejszenie liczby odczytów o około 25% (ponieważ tabela bazowa zachowuje dość dużo pustego miejsca na informacje o wersji), gdy ta opcja jest wyłączona, działa mniej kompaktowo (prawdopodobnie ze względu na bigint vs int różnica) i więcej odczytów wynik. Zmniejsza to lukę między zapytaniem podrzędnym a grupowaniem według wersji, ale zapytanie podrzędne nadal wygrywa.

Wyraźnym zwycięzcą był jednak rekursywny CTE. Dla wersji „bez przerw” odczyty logiczne z tabeli bazowej wynoszą teraz 2 x (n + 1) odzwierciedla n index szuka w indeksie 2 poziomu, aby pobrać wszystkie wiersze plus dodatkowy na końcu, który nic nie zwraca i kończy rekurencję. To nadal oznaczało 20,002 odczytów, aby przetworzyć 22-stronicową tabelę!

Logiczne odczyty tabeli roboczej dla wersji rekurencyjnej CTE są bardzo wysokie. Wydaje się, że działa przy 6 odczytach z tabeli roboczej na wiersz źródłowy. Pochodzą one ze szpuli indeksu, która przechowuje dane wyjściowe poprzedniego wiersza, a następnie jest odczytywana ponownie w następnej iteracji (dobre wyjaśnienie tego przez Umachandara Jayachandrana tutaj ). Pomimo dużej liczby jest to nadal najlepszy produkt.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Porównanie maszyn wirtualnych w chmurze z zarządzaną bazą danych w chmurze

  2. Najbardziej efektywny sposób na ograniczenie liczby wierszy zwracanych z zapytania składającego — TSQL

  3. Utwórz zadanie agenta serwera SQL przy użyciu T-SQL

  4. Jak wybrać określoną liczbę znaków z lewej lub prawej strony ciągu w SQL Server?

  5. Scal 2 bazy danych SQL Server