sql >> Databasteknik >  >> RDS >> Sqlserver

subquery eller leftjoin with group, vilken är snabbast?

En bra resurs för att beräkna löpande totaler i SQL Server är det här dokumentet av Itzik Ben Gan som skickades till SQL Server Team som en del av hans kampanj för att få OVER klausul utvidgades längre från dess initiala SQL Server 2005-implementering. I den visar han hur när du väl kommer in i tiotusentals rader, så utför markörerna uppsättningsbaserade lösningar. SQL Server 2012 utökade verkligen OVER klausul som gör den här typen av frågor mycket enklare.

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

Eftersom du använder SQL Server 2005 är detta dock inte tillgängligt för dig.

Adam Machanic visar här hur CLR kan användas för att förbättra prestandan för standard TSQL-markörer.

För denna tabelldefinition

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

Jag skapar tabeller med både 2 000 och 10 000 rader i en databas med ALLOW_SNAPSHOT_ISOLATION PÅ och en med den här inställningen av (Anledningen till detta är att mina initiala resultat var i en DB med inställningen på som ledde till en förbryllande aspekt av resultaten).

De klustrade indexen för alla tabeller hade bara 1 rotsida. Antalet bladsidor för varje visas nedan.

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

Jag testade följande fall (länkar visar genomförandeplaner)

  1. Vänster Gå med och Gruppera av
  2. Korrelerad underfråga 2000 radplan ,10 000 rader
  3. CTE från Mikaels (uppdaterade) svar
  4. CTE nedan

Anledningen till att det ytterligare CTE-alternativet inkluderades var för att tillhandahålla en CTE-lösning som fortfarande skulle fungera om ind kolumnen var inte garanterad sekventiell.

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

Alla frågorna hade en CAST(col1 AS BIGINT) läggs till för att undvika spillfel vid körning. Dessutom tilldelade jag resultaten för dem alla till variabler enligt ovan för att eliminera tid som går åt till att skicka tillbaka resultat från övervägande.

Resultat

+------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
|                  |          |        |          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 korrelerade underfrågan och GROUP BY version använder "triangulära" kapslade loop-kopplingar som drivs av en klustrad indexskanning på RunningTotals tabell (T1 ) och, för varje rad som returneras av den skanningen, söka tillbaka till tabellen (T2 ) går själv med på T2.ind<=T1.ind .

Detta innebär att samma rader bearbetas upprepade gånger. När T1.ind=1000 rad bearbetas självanslutningen hämtar och summerar alla rader med en ind <=1000 , sedan för nästa rad där T1.ind=1001 samma 1000 rader hämtas igen och summeras tillsammans med ytterligare en rad och så vidare.

Det totala antalet sådana operationer för en tabell med 2 000 rader är 2 001 000, för 10 000 rader 50 005 000 eller mer generellt (n² + n) / 2 som tydligt växer exponentiellt.

I fallet med 2 000 rader är den största skillnaden mellan GROUP BY och versionerna av underfrågan är att den förra har flödesaggregatet efter sammanfogningen och så har tre kolumner som matas in i den (T1.ind , T2.col1 , T2.col1 ) och en GROUP BY egenskapen för T1.ind medan den senare beräknas som ett skalärt aggregat, med strömaggregatet före sammanfogningen, endast har T2.col1 matas in i den och har ingen GROUP BY egendomsuppsättning alls. Detta enklare arrangemang kan ses ha en mätbar fördel i form av minskad CPU-tid.

För fallet med 10 000 rader finns det en ytterligare skillnad i underfrågeplanen. Den lägger till en ivrig spole som kopierar alla ind,cast(col1 som bigint) värden till tempdb . I det fall att ögonblicksbildsisolering är på detta fungerar det mer kompakt än den klustrade indexstrukturen och nettoeffekten är att minska antalet läsningar med cirka 25 % (eftersom bastabellen bevarar ganska mycket tomt utrymme för versionsinformation), när det här alternativet är avstängt fungerar det mindre kompakt (förmodligen på grund av bigint). kontra int skillnad) och resultatet av fler läsningar. Detta minskar gapet mellan underfrågan och grupp efter version, men underfrågan vinner fortfarande.

Den klara vinnaren var dock Recursive CTE. För versionen "inga luckor" är logiska läsningar från bastabellen nu 2 x (n + 1) återspeglar n index söker in i 2-nivåindexet för att hämta alla rader plus den ytterligare i slutet som inte returnerar något och avslutar rekursionen. Det innebar ändå 20 002 läsningar för att bearbeta en tabell på 22 sidor!

Logiska arbetstabellsavläsningar för den rekursiva CTE-versionen är mycket höga. Det verkar fungera med 6 arbetsbordsläsningar per källrad. Dessa kommer från indexspolen som lagrar utdata från föregående rad och läses sedan av igen i nästa iteration (bra förklaring av detta av Umachandar Jayachandran här ). Trots det höga antalet presterar detta fortfarande bäst.



  1. Använder Geekbench 3.2 för att testa stora databasservrar

  2. Gå med i samma tillfälliga bord i MySQL

  3. Oracle UTL_HTTP Post Multipart/Form-Data (JSON &ZIP) Exempel

  4. Prestandaskillnad i fråga mellan cmd och workbench mysql