Sqlserver
 sql >> Datenbank >  >> RDS >> Sqlserver

subquery oder leftjoin mit gruppe womit man schneller ist?

Eine großartige Ressource zur Berechnung laufender Summen in SQL Server ist dieses Dokument von Itzik Ben Gan, der dem SQL Server-Team als Teil seiner Kampagne für den OVER vorgelegt wurde -Klausel gegenüber der ursprünglichen SQL Server 2005-Implementierung weiter erweitert. Darin zeigt er, wie der Cursor, sobald Sie in Zehntausende von Zeilen gelangen, mengenbasierte Lösungen ausführt. SQL Server 2012 hat tatsächlich den OVER erweitert -Klausel, die diese Art von Abfrage viel einfacher macht.

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

Da Sie sich auf SQL Server 2005 befinden, steht Ihnen dies jedoch nicht zur Verfügung.

Adam Machanic zeigt hier wie die CLR verwendet werden kann, um die Leistung von Standard-TSQL-Cursorn zu verbessern.

Für diese Tabellendefinition

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

Ich erstelle Tabellen mit 2.000 und 10.000 Zeilen in einer Datenbank mit ALLOW_SNAPSHOT_ISOLATION ON und eine mit dieser Einstellung (Der Grund dafür ist, dass meine anfänglichen Ergebnisse in einer DB mit der Einstellung auf waren, die zu einem rätselhaften Aspekt der Ergebnisse führte).

Die gruppierten Indizes für alle Tabellen hatten nur 1 Stammseite. Die Anzahl der Blattseiten ist unten angegeben.

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

Ich habe die folgenden Fälle getestet (Links zeigen Ausführungspläne)

  1. Beitreten links und Gruppieren nach
  2. Korrelierte Unterabfrage 2000-Zeilenplan ,10000-Zeilen-Plan
  3. CTE aus Mikaels (aktualisierter) Antwort
  4. CTE unten

Der Grund für die Einbeziehung der zusätzlichen CTE-Option bestand darin, eine CTE-Lösung bereitzustellen, die auch dann noch funktioniert, wenn ind Spalte war nicht garantiert sequentiell.

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 Abfragen hatten einen CAST(col1 AS BIGINT) hinzugefügt, um Überlauffehler zur Laufzeit zu vermeiden. Zusätzlich habe ich für alle die Ergebnisse wie oben Variablen zugewiesen, um die Zeit zu eliminieren, die für das Zurücksenden von Ergebnissen aufgewendet wurde.

Ergebnisse

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

Sowohl die korrelierte Unterabfrage als auch GROUP BY Version verwenden "dreieckige" Joins mit verschachtelten Schleifen, die von einem Clustered-Index-Scan auf den RunningTotals gesteuert werden Tabelle (T1 ) und suche für jede von diesem Scan zurückgegebene Zeile zurück in der Tabelle (T2 ) Selbstbeitritt auf T2.ind<=T1.ind .

Dies bedeutet, dass dieselben Zeilen wiederholt verarbeitet werden. Wenn T1.ind=1000 Zeile verarbeitet wird, ruft der Self-Join alle Zeilen mit einem ind <= 1000 ab und summiert sie , dann für die nächste Zeile mit T1.ind=1001 dieselben 1000 Zeilen werden erneut abgerufen und zusammen mit einer zusätzlichen Zeile summiert und so weiter.

Die Gesamtzahl solcher Operationen für eine Tabelle mit 2.000 Zeilen beträgt 2.001.000, für 10.000 Zeilen 50.005.000 oder allgemeiner (n² + n) / 2 die eindeutig exponentiell wächst.

Im Fall von 2.000 Zeilen ist der Hauptunterschied zwischen GROUP BY und die Unterabfrageversionen ist, dass die erstere das Stream-Aggregat nach dem Join hat und daher drei Spalten enthält (T1.ind , T2.col1 , T2.col1 ) und ein GROUP BY Eigenschaft von T1.ind wohingegen letzteres als skalares Aggregat berechnet wird, wobei das Stream-Aggregat vor dem Join nur T2.col1 hat hineinfüttert und kein GROUP BY hat Eigenschaftssatz überhaupt. Diese einfachere Anordnung hat einen messbaren Vorteil in Form von reduzierter CPU-Zeit.

Für den Fall mit 10.000 Zeilen gibt es einen zusätzlichen Unterschied im Unterabfrageplan. Es fügt eine eifrige Spule hinzu die alle ind,cast(col1 as bigint) kopiert Werte in tempdb . Für den Fall, dass die Snapshot-Isolation aktiviert ist, funktioniert dies kompakter als die Cluster-Indexstruktur, und der Nettoeffekt besteht darin, die Anzahl der Lesevorgänge um etwa 25 % zu reduzieren (da die Basistabelle ziemlich viel leeren Platz für Versionsinformationen behält). Wenn diese Option ausgeschaltet ist, funktioniert es weniger kompakt (vermutlich wegen der bigint vs int Unterschied) und es resultieren mehr Lesevorgänge. Dadurch wird die Lücke zwischen der Unterabfrage und den Gruppieren-nach-Versionen verringert, aber die Unterabfrage gewinnt immer noch.

Der klare Gewinner war jedoch der rekursive CTE. Für die "no gaps"-Version sind logische Lesevorgänge aus der Basistabelle jetzt 2 x (n + 1) was den n widerspiegelt index sucht im 2-Ebenen-Index, um alle Zeilen plus die zusätzliche am Ende abzurufen, die nichts zurückgibt und die Rekursion beendet. Das bedeutete jedoch immer noch 20.002 Lesevorgänge, um eine 22-Seiten-Tabelle zu verarbeiten!

Logische Arbeitstabellenlesevorgänge für die rekursive CTE-Version sind sehr hoch. Es scheint bei 6 Worktable-Lesevorgängen pro Quellzeile zu funktionieren. Diese stammen aus der Indexspule, die die Ausgabe der vorherigen Zeile speichert und dann in der nächsten Iteration erneut gelesen wird (gute Erklärung dazu von Umachandar Jayachandran hier ). Trotz der hohen Zahl ist dies immer noch der beste Performer.