it-swarm.com.de

Zwischenspeichert SQL Server das Ergebnis einer Tabellenwertfunktion mit mehreren Anweisungen?

Eine Funktion mit Tabellenwerten mit mehreren Anweisungen gibt ihr Ergebnis in einer Tabellenvariablen zurück.

Werden diese Ergebnisse jemals wiederverwendet oder wird die Funktion bei jedem Aufruf immer vollständig ausgewertet?

22
Paul White 9

Die Ergebnisse einer Tabellenwertfunktion mit mehreren Anweisungen (msTVF) werden nie zwischen Anweisungen (oder Verbindungen) zwischengespeichert oder wiederverwendet, aber es gibt einige Möglichkeiten, wie ein msTVF-Ergebnis wiederverwendet werden kann innerhalb dieselbe Anweisung. Insofern wird ein msTVF nicht unbedingt bei jedem Aufruf neu gefüllt.

Beispiel msTVF

Diese (absichtlich ineffiziente) msTVF gibt einen bestimmten Bereich von Ganzzahlen mit einem Zeitstempel in jeder Zeile zurück:

IF OBJECT_ID(N'dbo.IntegerRange', 'TF') IS NOT NULL
    DROP FUNCTION dbo.IntegerRange;
GO
CREATE FUNCTION dbo.IntegerRange (@From integer, @To integer)
RETURNS @T table 
(
    n integer PRIMARY KEY, 
    ts datetime DEFAULT CURRENT_TIMESTAMP
)
WITH SCHEMABINDING
AS
BEGIN
    WHILE @From <= @To
    BEGIN
        INSERT @T (n)
        VALUES (@From);

        SET @From = @From + 1;
    END;
    RETURN;
END;

Statische Tabellenvariable

Wenn alle Parameter des Funktionsaufrufs Konstanten (oder Laufzeitkonstanten) sind, füllt der Ausführungsplan das Ergebnis der Tabellenvariablen einmal. Der Rest des Plans kann viele Male auf die Tabellenvariable zugreifen. Die statische Natur der Tabellenvariablen kann aus dem Ausführungsplan erkannt werden. Zum Beispiel:

SELECT
    IR.n,
    IR.ts 
FROM dbo.IntegerRange(1, 5) AS IR
ORDER BY
    IR.n;

Gibt ein ähnliches Ergebnis zurück wie:

(Simple result

Der Ausführungsplan lautet:

(Simple execution plan

Der Sequenzoperator ruft zuerst den Tabellenwertfunktionsoperator auf, der die Tabellenvariable auffüllt (beachten Sie, dass dieser Operator keine Zeilen zurückgibt). Als Nächstes ruft die Sequenz ihre zweite Eingabe auf, die den Inhalt der Tabellenvariablen zurückgibt (in diesem Fall mithilfe eines Clustered Index Scan).

Das Werbegeschenk, dass der Plan ein 'statisches' Ergebnis für Tabellenvariablen verwendet, ist der Operator "Tabellenwertfunktion" unter einer Sequenz. Die Tabellenvariable muss einmal vollständig ausgefüllt werden, bevor der Rest des Plans ausgeführt werden kann.

Mehrfachzugriffe

Um anzuzeigen, dass mehrmals auf das Ergebnis der Tabellenvariablen zugegriffen wird, verwenden wir eine zweite Tabelle mit Zeilen von 1 bis 5:

IF OBJECT_ID(N'dbo.T', 'U') IS NOT NULL
    DROP TABLE dbo.T;

CREATE TABLE dbo.T (i integer NOT NULL);

INSERT dbo.T (i) 
VALUES (1), (2), (3), (4), (5);

Und eine neue Abfrage, die diese Tabelle mit unserer Funktion verbindet (dies könnte auch als APPLY geschrieben werden):

SELECT T.i,
       IR.n,
       IR.ts
FROM dbo.T AS T
JOIN dbo.IntegerRange(1, 5) AS IR
    ON IR.n = T.i;

Das Ergebnis ist:

(Join result

Der Ausführungsplan:

(Join plan

Wie zuvor füllt die Sequenz zuerst das Ergebnis der Tabellenvariablen msTVF. Als nächstes werden verschachtelte Schleifen verwendet, um jede Zeile aus der Tabelle T mit einer Zeile aus dem msTVF-Ergebnis zu verbinden. Da die Funktionsdefinition einen hilfreichen Index für die Tabellenvariable enthielt, kann eine Indexsuche verwendet werden.

Der entscheidende Punkt ist, dass, wenn die Parameter für die msTVF Konstanten (einschließlich Variablen und Parameter) sind oder von der Ausführungs-Engine als Laufzeitkonstanten für die Anweisung behandelt werden, der Plan zwei separate Operatoren für das Ergebnis der msTVF-Tabellenvariablen enthält: einen zum Auffüllen der Tabelle; eine andere, um auf die Ergebnisse zuzugreifen, möglicherweise mehrmals auf die Tabelle zuzugreifen und möglicherweise Indizes zu verwenden, die in der Funktionsdefinition deklariert sind.

Korrelierte Parameter und nicht konstante Parameter

Um die Unterschiede hervorzuheben, wenn korrelierte Parameter (äußere Referenzen) oder nicht konstante Funktionsparameter verwendet werden, ändern wir den Inhalt der Tabelle T, damit die Funktion viel mehr Arbeit zu erledigen hat:

TRUNCATE TABLE dbo.T;

INSERT dbo.T (i) 
VALUES (50001), (50002), (50003), (50004), (50005);

Die folgende geänderte Abfrage verwendet jetzt einen äußeren Verweis auf die Tabelle T in einem der Funktionsparameter:

SELECT T.i,
       IR.n,
       IR.ts
FROM dbo.T AS T
CROSS APPLY dbo.IntegerRange(1, T.i) AS IR
WHERE IR.n = T.i;

Diese Abfrage dauert ungefähr 8 Sekunden , um Ergebnisse wie folgt zurückzugeben:

(Correlated result

Beachten Sie den Zeitunterschied zwischen den Zeilen in Spalte ts. Die WHERE -Klausel begrenzt das Endergebnis für eine Ausgabe mit vernünftiger Größe, aber die ineffiziente Funktion benötigt noch eine Weile, um die Tabellenvariable mit 50.000 ungeraden Zeilen zu füllen (abhängig vom korrelierten Wert von i) aus Tabelle T).

Der Ausführungsplan lautet:

(Correlated execution plan

Beachten Sie das Fehlen eines Sequenzoperators. Jetzt gibt es einen einzelnen Operator für Tabellenwertfunktionen, der die Tabellenvariable auffüllt und ihre Zeilen bei jeder Iteration des Joins verschachtelter Schleifen zurückgibt.

Um es klar auszudrücken: Mit nur 5 Zeilen in Tabelle T wird der Operator "Tabellenwertfunktion" fünfmal ausgeführt. Es generiert 50.001 Zeilen bei der ersten Iteration, 50.002 bei der zweiten ... und so weiter. Die Tabellenvariable wird zwischen den Iterationen "weggeworfen" (abgeschnitten), sodass jeder der fünf Aufrufe eine vollständige Grundgesamtheit ist. Aus diesem Grund ist es so langsam und es dauert ungefähr dieselbe Zeit, bis jede Zeile im Ergebnis angezeigt wird.

Randnotizen:

Das obige Szenario wurde natürlich absichtlich entwickelt, um zu zeigen, wie schlecht die Leistung sein kann, wenn der msTVF bei jeder Iteration viele Zeilen auffüllt.

Eine sinnvoll Implementierung des obigen Codes würde beide msTVF-Parameter auf i setzen und die redundante WHERE -Klausel entfernen. Die Tabellenvariable wird bei jeder Iteration immer noch abgeschnitten und neu gefüllt, jedoch jeweils nur mit einer Zeile.

Wir könnten auch die minimalen und maximalen i -Werte von T abrufen und sie in einem vorherigen Schritt in Variablen speichern. Das Aufrufen der Funktion mit Variablen anstelle von korrelierten Parametern würde es ermöglichen, das 'statische' Tabellenvariablenmuster wie zuvor erwähnt zu verwenden.

Caching für unveränderte korrelierte Parameter

Zurück zur Beantwortung der ursprünglichen Frage, bei der das statische Muster der Sequenz nicht verwendet werden kann, kann SQL Server vermeiden, dass die msTVF-Tabellenvariable abgeschnitten und neu gefüllt wird, wenn keine von Die korrelierten Parameter haben sich seit der vorherigen Iteration eines verschachtelten Loop-Joins geändert.

Um dies zu demonstrieren, werden wir den Inhalt von T durch fünf identischi Werte ersetzen:

TRUNCATE TABLE dbo.T;

INSERT dbo.T (i) 
VALUES (50005), (50005), (50005), (50005), (50005);

Die Abfrage mit einem korrelierten Parameter erneut:

SELECT T.i,
       IR.n,
       IR.ts
FROM dbo.T AS T
CROSS APPLY dbo.IntegerRange(1, T.i) AS IR
WHERE IR.n = T.i;

Diesmal erscheinen die Ergebnisse in ungefähr 1,5 Sekunden :

(Identical row results

Beachten Sie die identischen Zeitstempel in jeder Zeile. Das zwischengespeicherte Ergebnis in der Tabellenvariablen wird für nachfolgende Iterationen wiederverwendet, bei denen der korrelierte Wert i unverändert bleibt. Die Wiederverwendung des Ergebnisses ist viel schneller als das Einfügen von jeweils 50.005 Zeilen.

Der Ausführungsplan sieht sehr ähnlich aus wie zuvor:

(Plan for identical rows

Der Hauptunterschied besteht in den Eigenschaften Actual Rebinds und Actual Rewinds der Tabellenwert Funktionsoperator:

(Operator properties

Wenn sich die korrelierten Parameter nicht ändern, kann SQL Server die aktuellen Ergebnisse in der Tabellenvariablen wiedergeben (zurückspulen). Wenn sich die Korrelation ändert, muss SQL Server die Tabellenvariable abschneiden und neu füllen (neu binden). Die erneute Bindung erfolgt bei der ersten Iteration. Die vier folgenden Iterationen sind alle Rückspulen seit dem Wert von T.i ist unverändert.

23
Paul White 9