Feld = Parameter ODER Parameter IST NULL Muster


7

Mir sind die Parameter-Sniffing-Probleme bekannt, die mit gespeicherten Prozeduren verbunden sind, die mit einem Prädikat wie dem folgenden geschrieben wurden:

CREATE PROCEDURE [dbo].[Get] @Parameter INT = NULL AS BEGIN;
    SELECT [Field] FROM [dbo].[Table]
    WHERE [Field] = @Parameter 
    OR @Parameter IS NULL;
END;

Abhängig vom Wert des Parameters Scalar oder NULL bei der ersten Ausführung wird ein Plan zwischengespeichert, der für den entgegengesetzten Wert wahrscheinlich nicht optimal ist.

Angenommen, [Feld] ist skalar und der Clustering-Index für eine Tabelle. Was sind die Vor- und Nachteile der folgenden Ansätze zum Schreiben einer gespeicherten Prozedur (en) zur Unterstützung der Abfrage:

Bedingte Auswahl in derselben gespeicherten Prozedur

CREATE PROCEDURE [dbo].[Get] @Parameter INT = NULL AS BEGIN;
    IF(@Parameter IS NOT NULL) BEGIN;
        SELECT [Field]
        FROM [dbo].[Table]
        WHERE [Field] = @Parameter;
    END;
    ELSE BEGIN;
        SELECT [Field]
        FROM [dbo].[Table];
    END;
END;

Dynamisches SQL innerhalb der gespeicherten Prozedur

CREATE PROCEDURE [dbo].[Get] @Parameter INT = NULL AS BEGIN;
    DECLARE @sql NVARCHAR(MAX) = N'';
    SET @sql += N'SELECT [Field]'
    SET @sql += N'FROM [dbo].[Table]';

    IF(@Parameter IS NOT NULL) BEGIN;
        @sql += N'WHERE [Field] = @Parameter';
    END;

    SET @sql += N';';

    EXEC sp_executesql @sql N'@Parameter INT', @Parameter;
END;

Getrennte gespeicherte Prozeduren

CREATE PROCEDURE [dbo].[Get] @Parameter INT = NULL AS BEGIN;
    SELECT [Field]
    FROM [dbo].[Table]
    WHERE [Field] = @Parameter;
END;

CREATE PROCEDURE [dbo].[GetAll] AS BEGIN;
    SELECT [Field]
    FROM [dbo].[Table];
END;

5
Ich bin ein großer Fan des dynamischen SQL-Ansatzes, da Sie jede einzelne Parameterkombination sehr einfach optimieren können, wenn Sie müssen. Siehe blogs.sentryone.com/aaronbertrand/…
Aaron Bertrand

2
Der erste scheint Probleme mit dem Parameter-Sniffing zu verursachen, wenn er für NULL kompiliert wird. Ich stelle mir vor, dass der NOT NULL-Zweig bei einer Schätzung von 1 Zeile hängen bleibt.
Martin Smith

@Martin Smith - Ich dachte das Gleiche, aber das ist nicht der Fall. Diese Frage geht detailliert darauf ein, was tatsächlich passieren wird: dba.stackexchange.com/questions/185252/… .
M. Jacobson

@ M.Jacobson diese Antwort bestätigt, was ich dachte, würde passieren. Alle Anweisungen werden gemäß dem ersten übergebenen Parameterwert kompiliert. Wenn das NULL ist, =NULLwerden 0 Zeilen zurückgegeben. Obwohl oft Null-Zeilen-Schätzungen auf 1 aufgerundet werden
Martin Smith

@ Martin Smith - Entschuldigung, ich habe Ihre Aussage falsch interpretiert.
M. Jacobson

Antworten:


5

Sie sind alle ausgezeichnet. Ja wirklich. Sie alle haben die gleiche Auswirkung, wenn zwei Pläne im Cache gespeichert sind.

Wenn Sie mehr und mehr Parameter erhalten, werden Sie feststellen, dass die Option Dynamic SQL am klarsten ist, auch wenn sie für Anfänger beängstigender erscheint.

Wenn dies eine Funktion wäre, würde ich vorschlagen, Optionen mit mehreren Anweisungen zu vermeiden, damit die Qualitätssicherung ihre Aufgaben besser erledigen kann.


5

Wenn Sie sich in einem relativ neuen SQL Server-Build befinden, können Sie auch eine andere Option in Betracht ziehen

SELECT [Field]
FROM   [dbo].[Table]
WHERE  [Field] = @Parameter
        OR @Parameter IS NULL
OPTION (RECOMPILE); 

Jedes Mal auf Kosten einer Kompilierung einen optimalen Plan für den Laufzeitwert erhalten.

Ihre Option "Bedingte Auswahl" ist weiterhin anfällig für Parameter-Sniffing. Wenn die Prozedur zum ersten Mal ausgeführt wird, wenn sie @Parameternull ist, [Field] = @Parameterschätzt der Zweig mit dem Prädikat 1 Zeilen (aufgerundet von der für ein =NULLPrädikat erwarteten 0 ).

In dem speziellen Beispiel in Ihrer Frage, in dem Sie eine einzelne Spalte auswählen und das ist dieselbe Spalte, nach der Sie filtern, ist es unwahrscheinlich, dass dies ein Problem darstellt, dies kann jedoch in anderen Fällen der Fall sein.

Beispiel: Im folgenden Beispiel werden beim ersten Aufruf [dbo].[Get] 1333.731 logische Lesevorgänge ausgeführt, da ein unangemessener Plan mit Schlüsselsuchen ausgewählt wird. Wenn der Plan aus dem Cache entfernt und mit 1zuerst übergeben neu kompiliert wird, fallen die logischen Lesevorgänge auf 4.330

DROP TABLE IF EXISTS [Table]

GO

CREATE TABLE [Table]
(
[Field1]  INT INDEX IX,
[Field2]  INT,
[Field3]  INT,
);

INSERT INTO [Table]
SELECT TOP 1000000 CRYPT_GEN_RANDOM(1)%3, CRYPT_GEN_RANDOM(4), CRYPT_GEN_RANDOM(4)
FROM sys.all_objects o1, sys.all_objects o2

GO

CREATE OR ALTER PROCEDURE [dbo].[Get] @Parameter INT = NULL AS BEGIN;
    IF(@Parameter IS NOT NULL) BEGIN;
        SELECT *
        FROM [dbo].[Table]
        WHERE [Field1] = @Parameter;
    END;
    ELSE BEGIN;
        SELECT *
        FROM [dbo].[Table];
    END;
END;

GO

SET STATISTICS TIME ON
SET STATISTICS IO ON


EXEC [dbo].[Get] 

EXEC [dbo].[Get] 1;

declare @plan_handle varbinary(64) = (select plan_handle from sys.dm_exec_procedure_stats where object_id = object_id('[dbo].[Get]'));

--Remove the plan from the cache 
DBCC FREEPROCCACHE (@plan_handle);  

--Re-execute it with NOT NULL passed first
EXEC [dbo].[Get] 1;

2

Basierend auf den vorherigen Antworten und Kommentaren von Aaron Bertrand, Martin Smith und Rob Farley. Ich wollte für jeden Ansatz eine Pro / Contra-Liste zusammenstellen, einschließlich des zusätzlichen Ansatzes OPTION (RECOMPILE):


Bedingte Auswahl in derselben gespeicherten Prozedur

Aus Martin Smiths Antwort:

Ihre Option "Bedingte Auswahl" ist weiterhin anfällig für Parameter-Sniffing. Wenn die Prozedur zum ersten Mal ausgeführt wird, wenn @Parameter null ist, schätzt der Zweig mit dem Prädikat [Field] = @Parameter 1 Zeilen (aufgerundet von der für ein Prädikat = NULL erwarteten 0).

  • Keine Kosten für die Neukompilierung.
  • Planen Sie die Wiederverwendung des Caches für jede Anweisung und die gespeicherte Prozedur.
  • Zwischengespeicherte Pläne sind anfällig für das Abhören von Parametern, selbst wenn keine signifikante Abweichung in der Ergebnismenge vorliegt, wenn @Parameter NICHT NULL ist.
  • Skaliert administrativ nicht gut, wenn die Anzahl der Parameter zunimmt.
  • Intellisense auf allen T-SQL.

Dynamisches SQL innerhalb der gespeicherten Prozedur

Von Rob Farley:

Wenn Sie mehr und mehr Parameter erhalten, werden Sie feststellen, dass die Option Dynamic SQL am klarsten ist, auch wenn sie für Anfänger beängstigender erscheint.

  • Keine Kosten für die Neukompilierung.
  • Planen Sie die Wiederverwendung des Caches für jede Anweisung und die gespeicherte Prozedur.
  • Zwischengespeicherte Pläne sind nur dann für das Sniffing von Parametern anfällig, wenn die Ergebnismenge erheblich abweicht, wenn @Parameter NICHT NULL ist.
  • Skaliert sich administrativ gut, wenn die Anzahl der Parameter zunimmt.
  • Bietet nicht Intellisense für alle T-SQL-Anwendungen.

Getrennte gespeicherte Prozeduren

  • Keine Kosten für die Neukompilierung.
  • Planen Sie die Wiederverwendung des Caches für jede Anweisung und die gespeicherte Prozedur.
  • Zwischengespeicherte Pläne sind nur dann für das Sniffing von Parametern anfällig, wenn die Ergebnismenge erheblich abweicht, wenn @Parameter NICHT NULL ist.
  • Skaliert administrativ nicht gut, wenn die Anzahl der Parameter zunimmt.
  • Intellisense auf allen T-SQL.

OPTION (RECOMPILE)

Von Martin Smith:

Jedes Mal auf Kosten einer Kompilierung einen optimalen Plan für den Laufzeitwert zu erhalten.

  • CPU-Kosten für die Neukompilierung.
  • Keine Wiederverwendung des Plan-Cache für Anweisungen, gefolgt von OPTION (RECOMPILE), nur die gespeicherte Prozedur und Anweisungen ohne OPTION (RECOMPILE).
  • Skaliert sich administrativ gut, wenn die Anzahl der Parameter zunimmt.
  • Nicht anfällig für Parameter-Sniffing.
  • Intellisense auf allen T-SQL.

Mein persönlicher Imbiss

Wenn es keine signifikante Abweichung in der Ergebnismenge mit unterschiedlichen Skalarwerten von @Parameter gibt, ist Dynamic SQL ein Top-Performer, hat den geringsten Systemaufwand und ist im Vergleich zu OPTION (RECOMPILE) in Bezug auf den Verwaltungsaufwand nur unwesentlich schlechter. In komplexeren Szenarien, in denen Abweichungen im Parameterwert zu erheblichen Änderungen der Ergebnismengen führen können, ist die Verwendung von Dynamic SQL mit bedingtem Einschluss oder Ausschluss von OPTION (RECOMPILE) die beste Gesamtleistung. Hier ist ein Link zu Aaron Bertrands Artikel, der den Ansatz detailliert beschreibt: https://blogs.sentryone.com/aaronbertrand/backtobasics-updated-kitchen-sink-example/

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.