sql >> Databáze >  >> RDS >> Sqlserver

subquery nebo leftjoin se skupinou, která z nich je rychlejší?

Skvělým zdrojem pro výpočet průběžných součtů na SQL Serveru je tento dokument od Itzik Ben Gan, který byl předložen týmu SQL Server jako součást jeho kampaně, aby měl OVER klauzule rozšířena dále od své původní implementace SQL Server 2005. V něm ukazuje, jak jakmile se dostanete do desítek tisíc řádků kurzorů, provádíte řešení založená na množinách. SQL Server 2012 skutečně rozšířil OVER klauzule, která tento druh dotazů značně usnadňuje.

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

Vzhledem k tomu, že používáte SQL Server 2005, toto není pro vás dostupné.

Adam Machanic zobrazuje se zde jak lze CLR použít ke zlepšení výkonu standardních kurzorů TSQL.

Pro tuto definici tabulky

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

Vytvářím tabulky s 2 000 i 10 000 řádky v databázi se zapnutou ALLOW_SNAPSHOT_ISOLATION ON a jeden s tímto nastavením vypnutým (Důvodem je to, že moje počáteční výsledky byly v DB se zapnutým nastavením, které vedlo k záhadnému aspektu výsledků).

Seskupené indexy pro všechny tabulky měly pouze 1 kořenovou stránku. Počet listových stránek pro každou z nich je uveden níže.

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

Testoval jsem následující případy (Odkazy ukazují plány provádění)

  1. Připojit se a seskupit podle
  2. Korelovaný poddotaz plán 2000 řádků ,Plán 10 000 řádků
  3. CTE z Mikaelovy (aktualizované) odpovědi
  4. CTE níže

Důvodem pro zahrnutí další možnosti CTE bylo poskytnout řešení CTE, které by stále fungovalo, pokud by ind sloupec nebyl zaručen sekvenční.

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);

Všechny dotazy měly CAST(col1 AS BIGINT) přidány, aby se předešlo chybám při přetečení za běhu. Navíc u všech jsem přiřadil výsledky k proměnným, jak je uvedeno výše, abych eliminoval čas strávený zasíláním výsledků zpět z úvahy.

Výsledky

+------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
|                  |          |        |          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     |
+------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+

Jak korelovaný poddotaz, tak GROUP BY verze používá „trojúhelníková“ vnořená spojení smyčky řízená skenováním seskupeného indexu na RunningTotals tabulka (T1 ) a pro každý řádek vrácený tímto skenem vyhledání zpět do tabulky (T2 ) vlastní připojení na T2.ind<=T1.ind .

To znamená, že se opakovaně zpracovávají stejné řádky. Když T1.ind=1000 řádek je zpracován, vlastní spojení načte a sečte všechny řádky s ind <= 1000 a poté na další řádek, kde T1.ind=1001 stejných 1000 řádků se načte znovu a sečteno spolu s jedním dalším řádkem a tak dále.

Celkový počet takových operací pro tabulku s 2 000 řádky je 2 001 000, pro 10 tisíc řádků 50 005 000 nebo obecněji (n² + n) / 2 který zřetelně roste exponenciálně.

V případě 2 000 řádků je hlavní rozdíl mezi GROUP BY a verze poddotazu spočívá v tom, že první z nich má po spojení agregaci proudu, a tak se do něj vkládají tři sloupce (T1.ind , T2.col1 , T2.col1 ) a GROUP BY vlastnost T1.ind zatímco druhý je vypočítán jako skalární agregát, přičemž agregace proudu před spojením má pouze T2.col1 vkládá se do něj a nemá GROUP BY majetek vůbec. Toto jednodušší uspořádání může mít měřitelný přínos ve smyslu snížení času CPU.

Pro případ 10 000 řádků existuje další rozdíl v plánu dílčích dotazů. Přidává eager spool který zkopíruje všechny ind,cast(col1 as bigint) hodnoty do tempdb . V případě, že je izolace snímků zapnutá, funguje to kompaktněji než struktura klastrovaného indexu a výsledným efektem je snížení počtu čtení asi o 25 % (protože základní tabulka zachovává poměrně hodně prázdného místa pro informace o verzích), když je tato možnost vypnutá, je méně kompaktní (pravděpodobně kvůli bigint vs int rozdíl) a výsledkem je více čtení. Tím se zmenší mezera mezi dílčím dotazem a seskupením podle verzí, ale dílčí dotaz stále vítězí.

Jasným vítězem však byl rekurzivní CTE. Pro verzi "bez mezer" jsou nyní logické čtení ze základní tabulky 2 x (n + 1) odrážející n index hledá do 2úrovňového indexu, aby načetl všechny řádky plus další na konci, který nevrací nic a ukončuje rekurzi. To však stále znamenalo 20 002 čtení pro zpracování 22stránkové tabulky!

Hodnoty logických pracovních tabulek pro rekurzivní verzi CTE jsou velmi vysoké. Zdá se, že to funguje při 6 čteních pracovních tabulek na zdrojový řádek. Ty pocházejí z indexové cívky, která uchovává výstup předchozího řádku, a poté je znovu čtena v další iteraci (dobré vysvětlení toho uvádí Umachandar Jayachandran zde ). Navzdory vysokému počtu je to stále nejlepší výkon.



  1. Jak zjistím selhání při selhání převodu varchar na int v SQL?

  2. Specifikace Oracle nebo SQL? :Zkrácení tabulky s omezeními cizího klíče

  3. Vyberte náhodný řádek pro každou skupinu

  4. Jak vybrat entity voláním uložené procedury pomocí Spring Data