sql >> Database teknologi >  >> RDS >> Sqlserver

underforespørgsel eller leftjoin med gruppe, hvilken er hurtigere?

En fantastisk ressource til beregning af løbende totaler i SQL Server er dette dokument af Itzik Ben Gan, der blev sendt til SQL Server-teamet som en del af hans kampagne for at få OVER klausul udvidet længere fra dens oprindelige SQL Server 2005-implementering. I den viser han, hvordan når du først kommer ind i titusindvis af rækker, udfører markørerne sæt-baserede løsninger. SQL Server 2012 udvidede faktisk OVER klausul gør denne form for forespørgsel meget lettere.

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

Da du er på SQL Server 2005, er dette dog ikke tilgængeligt for dig.

Adam Machanic viser her hvordan CLR kan bruges til at forbedre ydeevnen af ​​standard TSQL-markører.

For denne tabeldefinition

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

Jeg opretter tabeller med både 2.000 og 10.000 rækker i en database med ALLOW_SNAPSHOT_ISOLATION TIL og en med denne indstilling fra (Grunden til dette er, fordi mine første resultater var i en DB med indstillingen på, der førte til et forvirrende aspekt af resultaterne).

De klyngede indekser for alle tabeller havde kun 1 rodside. Antallet af bladsider for hver er vist nedenfor.

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

Jeg testede følgende cases (Links viser udførelsesplaner)

  1. Tilføj til venstre og grupper efter
  2. Korreleret underforespørgsel 2000 rækkeplan ,10000 rækkeplan
  3. CTE fra Mikaels (opdaterede) svar
  4. CTE nedenfor

Årsagen til at inkludere den ekstra CTE-mulighed var at levere en CTE-løsning, der stadig ville fungere, hvis ind kolonne var ikke garanteret sekventiel.

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

Alle forespørgslerne havde en CAST(col1 AS BIGINT) tilføjet for at undgå overløbsfejl under kørsel. For dem alle tildelte jeg desuden resultaterne til variabler som ovenfor for at eliminere tid brugt på at sende resultater tilbage fra overvejelse.

Resultater

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

Både den korrelerede underforespørgsel og GROUP BY version bruger "triangulære" indlejrede løkkesammenføjninger drevet af en klynget indeksscanning på RunningTotals tabel (T1 ) og for hver række, der returneres af den scanning, søges tilbage i tabellen (T2 ) selvtilmelding på T2.ind<=T1.ind .

Det betyder, at de samme rækker bliver behandlet gentagne gange. Når T1.ind=1000 rækken er behandlet, selvforeningen henter og summerer alle rækker med en ind <=1000 , derefter til næste række hvor T1.ind=1001 de samme 1000 rækker hentes igen og summeret sammen med en ekstra række og så videre.

Det samlede antal af sådanne operationer for en tabel med 2.000 rækker er 2.001.000, for 10.000 rækker 50.005.000 eller mere generelt (n² + n) / 2 som tydeligvis vokser eksponentielt.

I tilfældet med 2.000 rækker er den største forskel mellem GROUP BY og underforespørgselsversionerne er, at førstnævnte har stream-aggregatet efter joinforbindelsen, og så har tre kolonner, der føres ind i det (T1.ind , T2.col1 , T2.col1 ) og en GROUP BY egenskaben for T1.ind hvorimod sidstnævnte beregnes som et skalært aggregat, hvor strømaggregatet før sammenføjningen kun har T2.col1 feeds ind i det og har ingen GROUP BY ejendomssæt overhovedet. Dette enklere arrangement kan ses at have en målbar fordel i form af reduceret CPU-tid.

For sagen med 10.000 rækker er der en yderligere forskel i underforespørgselsplanen. Den tilføjer en ivrig spole som kopierer alle ind,cast(col1 som bigint) værdier i tempdb . I det tilfælde, hvor snapshot-isolering er på, virker dette mere kompakt end den klyngede indeksstruktur, og nettoeffekten er at reducere antallet af læsninger med omkring 25 % (da basistabellen bevarer en hel del tom plads til versionsinformation), når denne mulighed er slået fra, virker den mindre kompakt (formodentlig på grund af bigint). vs int forskel) og resultatet af flere læsninger. Dette reducerer kløften mellem underforespørgslen og gruppen efter version, men underforespørgslen vinder stadig.

Den klare vinder var dog Recursive CTE. For versionen "ingen mellemrum" er logiske læsninger fra basistabellen nu 2 x (n + 1) afspejler n indeks søger ind i indekset på 2 niveauer for at hente alle rækkerne plus den ekstra i slutningen, der ikke returnerer noget og afslutter rekursionen. Det betød dog stadig 20.002 læsninger for at behandle en 22 siders tabel!

Logiske arbejdstabellæsninger for den rekursive CTE-version er meget høje. Det ser ud til at fungere ved 6 arbejdsbordlæsninger pr. kilderække. Disse kommer fra indeksspolen, der gemmer output fra den foregående række, og derefter læses fra igen i næste iteration (god forklaring af dette af Umachandar Jayachandran her ). På trods af det høje antal er dette stadig den bedste performer.



  1. SQL Server 2016 på Linux

  2. Weblogic Deployment Exception:PaddingException:Kunne ikke udføre unpadding:ugyldig pad-byte

  3. Hvor vigtig er rækkefølgen af ​​kolonner i indekser?

  4. MySQL-forespørgselsrækkefølge efter de fleste udfyldte felter