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

Hilft das Zuweisen von Eingabeparametern für gespeicherte Prozeduren zu lokalen Variablen bei der Optimierung der Abfrage?

Ich werde nicht versuchen, die vollständigen Details des Parameter-Sniffing zu erklären, aber kurz gesagt, nein, das tut es nicht immer helfen (und es kann hindern).

Stellen Sie sich eine Tabelle (T) mit einem Primärschlüssel und einer indizierten Datumsspalte (A) vor, in der Tabelle gibt es 1.000 Zeilen, 400 haben denselben Wert von A (sagen wir heute 20130122), die restlichen 600 Zeilen sind die nächsten 600 Tage , also nur 1 Datensatz pro Datum.

Diese Abfrage:

SELECT *
FROM T
WHERE A = '20130122';

Ergibt einen anderen Ausführungsplan für:

SELECT *
FROM T
WHERE A = '20130123';

Da die Statistiken anzeigen, dass für die ersten 400 von 1.000 Zeilen zurückgegeben werden, sollte der Optimierer erkennen, dass ein Tabellenscan effizienter ist als eine Lesezeichensuche, während der zweite nur 1 Zeile ergibt, sodass eine Lesezeichensuche viel kostet effizienter.

Nun zurück zu Ihrer Frage, ob wir dies zu einer Prozedur gemacht haben:

CREATE PROCEDURE dbo.GetFromT @Param DATE
AS
    SELECT *
    FROM T
    WHERE A = @Param

Dann ausführen

EXECUTE dbo.GetFromT '20130122'; --400 rows

Der Abfrageplan mit dem Tabellenscan wird verwendet. Wenn Sie ihn beim ersten Ausführen als Parameter verwenden, wird der Lesezeichen-Suchplan gespeichert. Bis die Prozedur neu kompiliert wird, bleibt der Plan derselbe. So etwas tun:

CREATE PROCEDURE dbo.GetFromT @Param VARCHAR(5)
AS
    DECLARE @Param2 VARCHAR(5) = @Param;
    SELECT *
    FROM T
    WHERE A = @Param2

Dann wird dies ausgeführt:

EXECUTE dbo.GetFromT '20130122';

Während die Prozedur in einem Rutsch kompiliert wird, fließt sie nicht richtig, sodass der bei der ersten Kompilierung erstellte Abfrageplan keine Ahnung hat, dass @Param2 dasselbe wie @param wird, sodass der Optimierer (ohne zu wissen, wie viele Zeilen zu erwartet) wird davon ausgegangen, dass 300 zurückgegeben werden (30 %), da dies einen Tabellenscan als effizienter erachtet als eine Lesezeichensuche. Wenn Sie dieselbe Prozedur mit „20130123“ als Parameter ausführen, würde dies denselben Plan ergeben (unabhängig davon, mit welchem ​​Parameter er zuerst aufgerufen wurde), da die Statistiken nicht für einen unbekannten Wert verwendet werden können. Das Ausführen dieser Prozedur für „20130122“ wäre also effizienter, aber für alle anderen Werte weniger effizient als ohne lokale Parameter (vorausgesetzt, die Prozedur ohne lokale Parameter wurde zuerst mit etwas anderem als „20130122“ aufgerufen)

Einige zu demonstrierende Abfragen, damit Sie Ausführungspläne selbst anzeigen können

Erstellen Sie Schema- und Beispieldaten

CREATE TABLE T (ID INT IDENTITY(1, 1) PRIMARY KEY, A DATE NOT NULL, B INT,C INT, D INT, E INT);

CREATE NONCLUSTERED INDEX IX_T ON T (A);

INSERT T (A, B, C, D, E)
SELECT  TOP 400 CAST('20130122' AS DATE), number, 2, 3, 4 
FROM    Master..spt_values 
WHERE   type = 'P'
UNION ALL
SELECT TOP 600 DATEADD(DAY, number, CAST('20130122' AS DATE)), number, 2, 3, 4 
FROM    Master..spt_values 
WHERE   Type = 'P';
GO
CREATE PROCEDURE dbo.GetFromT @Param DATE
AS
    SELECT *
    FROM T
    WHERE A = @Param
GO
CREATE PROCEDURE dbo.GetFromT2 @Param DATE
AS
    DECLARE @Param2 DATE = @Param;
    SELECT *
    FROM T
    WHERE A = @Param2
GO

Ausführen von Prozeduren (zeigt den tatsächlichen Ausführungsplan):

EXECUTE GetFromT '20130122';
EXECUTE GetFromT '20130123';
EXECUTE GetFromT2 '20130122';
EXECUTE GetFromT2 '20130123';
GO
EXECUTE SP_RECOMPILE GetFromT;
EXECUTE SP_RECOMPILE GetFromT2;
GO
EXECUTE GetFromT '20130123';
EXECUTE GetFromT '20130122';
EXECUTE GetFromT2 '20130123';
EXECUTE GetFromT2 '20130122';

Sie werden sehen, dass beim ersten Mal GetFromT kompiliert wird, verwendet es einen Tabellenscan und behält diesen bei, wenn es mit dem Parameter „20130122“, GetFromT2 ausgeführt wird verwendet auch einen Tabellenscan und behält den Plan für '20130122' bei.

Nachdem die Prozeduren für die Neukompilierung eingestellt und erneut ausgeführt wurden (Notiz in einer anderen Reihenfolge), GetFromT verwendet ein Lesezeichen-Loopup und behält den Plan für '20130122' bei, obwohl er zuvor der Meinung war, dass ein Tabellenscan ein geeigneterer Plan ist. GetFromT2 ist von der Bestellung nicht betroffen und hat den gleichen Plan wie vor der Neubesetzung.

Zusammenfassend hängt es also von der Verteilung Ihrer Daten und Ihrer Indizes, Ihrer Häufigkeit der Neukompilierung und etwas Glück ab, ob eine Prozedur von der Verwendung lokaler Variablen profitiert. Es ist sicherlich nicht immer Hilfe.

Hoffentlich habe ich etwas Licht in die Auswirkungen der Verwendung lokaler Parameter, Ausführungspläne und Kompilierung gespeicherter Prozeduren gebracht. Wenn ich komplett gescheitert bin oder einen wichtigen Punkt verpasst habe, finden Sie hier eine viel ausführlichere Erklärung:

http://www.sommarskog.se/query-plan-mysteries.html