sql >> Database >  >> RDS >> Sqlserver

subquery of leftjoin met groep door welke is sneller?

Een geweldige bron voor het berekenen van lopende totalen in SQL Server is dit document door Itzik Ben Gan die werd ingediend bij het SQL Server-team als onderdeel van zijn campagne om de OVER clausule verder uitgebreid van de oorspronkelijke implementatie van SQL Server 2005. Daarin laat hij zien hoe, als je eenmaal in tienduizenden rijen bent gekomen, cursors op set gebaseerde oplossingen uitvoeren. SQL Server 2012 heeft inderdaad de OVER . uitgebreid clausule die dit soort zoekopdrachten veel gemakkelijker maakt.

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

Aangezien u SQL Server 2005 gebruikt, is dit echter niet voor u beschikbaar.

Adam Machanic wordt hier weergegeven hoe de CLR kan worden gebruikt om de prestaties van standaard TSQL-cursors te verbeteren.

Voor deze tabeldefinitie

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

Ik maak tabellen met zowel 2.000 als 10.000 rijen in een database met ALLOW_SNAPSHOT_ISOLATION ON en een met deze instelling uit (de reden hiervoor is omdat mijn eerste resultaten in een DB waren met de instelling op die leidde tot een raadselachtig aspect van de resultaten).

De geclusterde indexen voor alle tabellen hadden slechts 1 hoofdpagina. Het aantal bladpagina's voor elk wordt hieronder weergegeven.

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

Ik heb de volgende gevallen getest (Links tonen uitvoeringsplannen)

  1. Links lid worden en groeperen op
  2. Gecorreleerde subquery 2000 rijenplan ,10000 rijenplan
  3. CTE van Mikael's (bijgewerkte) antwoord
  4. CTE hieronder

De reden voor het opnemen van de extra CTE-optie was om een ​​CTE-oplossing te bieden die nog steeds zou werken als de ind kolom was niet gegarandeerd sequentieel.

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 zoekopdrachten hadden een CAST(col1 AS BIGINT) toegevoegd om overloopfouten tijdens runtime te voorkomen. Bovendien heb ik voor al deze resultaten de resultaten toegewezen aan variabelen zoals hierboven om de tijd te elimineren die besteed wordt aan het terugsturen van resultaten.

Resultaten

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

Zowel de gecorreleerde subquery als de GROUP BY versie gebruik "driehoekige" geneste lus-joins aangedreven door een geclusterde indexscan op de RunningTotals tabel (T1 ) en, voor elke rij die door die scan wordt geretourneerd, terug in de tabel zoeken (T2 ) zelf lid worden op T2.ind<=T1.ind .

Dit betekent dat dezelfde rijen herhaaldelijk worden verwerkt. Wanneer de T1.ind=1000 rij wordt verwerkt, de self-join haalt alle rijen op en telt ze op met een ind <= 1000 , dan voor de volgende rij waar T1.ind=1001 dezelfde 1000 rijen worden opnieuw opgehaald en opgeteld met een extra rij enzovoort.

Het totale aantal van dergelijke bewerkingen voor een tabel met 2.000 rijen is 2.001.000, voor 10.000 rijen 50.000.000 of meer in het algemeen (n² + n) / 2 die duidelijk exponentieel groeit.

In het geval van 2000 rijen is het belangrijkste verschil tussen de GROUP BY en de subqueryversie is dat de eerste de stream-aggregaat heeft na de join en dus drie kolommen heeft die erin worden ingevoerd (T1.ind , T2.col1 , T2.col1 ) en een GROUP BY eigendom van T1.ind terwijl de laatste wordt berekend als een scalair aggregaat, waarbij het stroomaggregaat vóór de samenvoeging alleen T2.col1 heeft invoert en heeft geen GROUP BY eigenschap helemaal niet. Deze eenvoudigere opstelling heeft een meetbaar voordeel in termen van verminderde CPU-tijd.

Voor het geval van 10.000 rijen is er een extra verschil in het subqueryplan. Het voegt een gretige spoel toe die alle ind,cast(col1 as bigint) . kopieert waarden in tempdb . In het geval dat snapshot-isolatie is ingeschakeld, is dit compacter dan de geclusterde indexstructuur en het netto-effect is om het aantal leesbewerkingen met ongeveer 25% te verminderen (omdat de basistabel behoorlijk wat lege ruimte vrijhoudt voor versie-informatie), wanneer deze optie is uitgeschakeld, werkt het minder compact (vermoedelijk vanwege de bigint vs int verschil) en meer leest resultaat. Dit verkleint de kloof tussen de subquery en groeperen op versies, maar de subquery wint nog steeds.

De duidelijke winnaar was echter de Recursive CTE. Voor de "no gaps"-versie zijn de logische reads van de basistabel nu 2 x (n + 1) weerspiegelt de n index zoekt in de 2-niveau-index om alle rijen op te halen plus de extra aan het einde die niets retourneert en de recursie beëindigt. Dat betekende echter nog steeds 20.002 reads om een ​​tabel van 22 pagina's te verwerken!

Logische werktabellezingen voor de recursieve CTE-versie zijn erg hoog. Het lijkt te werken bij 6 werktabellezingen per bronrij. Deze komen van de indexspoel die de uitvoer van de vorige rij opslaat en vervolgens in de volgende iteratie opnieuw wordt gelezen (goede uitleg hiervan door Umachandar Jayachandran hier ). Ondanks het hoge aantal is dit nog steeds de best presterende.



  1. Waarom retourneert mysql_num_rows nul?

  2. Gebruik een foreach-lus in plaats van while met myslqli_fetch_array()

  3. Deel 1:Beeldclassificatie met MariaDB Server en TensorFlow - een overzicht

  4. Ondersteunt Python door MySQL voorbereide instructies?