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

SQL Server-Interna:Plan Caching Pt. I – Pläne wiederverwenden

SQL Server gibt es seit über 30 Jahren und ich arbeite fast genauso lange mit SQL Server. Ich habe im Laufe der Jahre (und Jahrzehnte!) und Versionen dieses unglaublichen Produkts viele Veränderungen gesehen. In diesen Beiträgen werde ich mit Ihnen teilen, wie ich einige der Funktionen oder Aspekte von SQL Server betrachte, manchmal zusammen mit einem kleinen historischen Blickwinkel.

Schauen Sie sich hier Kalens aktuelle Blogs zu problematischen Operatoren an.

Die Erstellung von Plänen für die SQL Server-Diagnose kann teuer sein, da der Abfrageoptimierer in der Lage sein muss, einen guten Plan für jede eingereichte rechtliche Abfrage zu finden. Der Optimierer wertet mehrere Join-Reihenfolgen und mehrere Indizes aus. und verschiedene Arten von Join- und Gruppierungsalgorithmen, abhängig von Ihrer Abfrage und den beteiligten Tabellen. Wenn dieselbe Abfrage erneut ausgeführt wird, kann SQL Server viele Ressourcen einsparen, indem ein vorhandener Plan wiederverwendet wird. Aber es ist nicht immer möglich, einen bestehenden Plan wiederzuverwenden, und es ist nicht immer gut, dies zu tun. In den nächsten beiden Artikeln sehen wir uns an, wann ein Plan wiederverwendet und wann er neu kompiliert wird.

Zuerst sehen wir uns die verschiedenen Arten von Plänen und die Metadatenansicht an, die ich am häufigsten verwende, um zu sehen, was sich in meinem Plan-Cache befindet. Ich habe eine eigene Ansicht geschrieben, die die Informationen enthält, die ich am häufigsten am nützlichsten finde. SQL Server speichert sechs verschiedene Arten von Abfrageplänen, aber normalerweise werden nur zwei für die Optimierung des Plancaches verwendet. Dies sind COMPILED PLAN und COMPILED PLAN STUB. Meine Ansicht filtert alle außer diesen beiden Arten von Cache-Objekten heraus. ZUSAMMENGESTELLTE PLÄNE gibt es in drei Varianten:AD HOC, PREPARED und PROC. Ich werde alle drei Arten kommentieren.

Selbst wenn wir uns nur COMPILED PLANS ansehen, gibt es immer noch viele Pläne im Cache, die normalerweise ignoriert werden müssen, da sie von SQL Server selbst generiert werden. Dazu gehören Pläne, die nach Filestream- oder Volltextsuchindizes oder internen Abfragen suchen, die mit In-Memory-OLTP arbeiten. Meine Ansicht fügt also Filter hinzu, um zu versuchen, die meisten Pläne, an denen ich nicht interessiert bin, auszumerzen. Sie können ein Skript namens sp_cacheobjects herunterladen, um diese Ansicht zu erstellen , von hier.

Selbst mit all den Filtern, die meine Ansicht verwendet, gibt es immer noch einige der eigenen internen Abfragen von SQL Server im Cache; Normalerweise lösche ich den Plan-Cache häufig, wenn ich in diesem Bereich Tests durchführe. Der einfachste Weg, ALLE Pläne aus dem Cache zu löschen, ist der Befehl:DBCC FREEPROCCACHE.

Ad-hoc-kompilierte Pläne

Der einfachste Plantyp ist Adhoc. Dies wird für grundlegende Abfragen verwendet, die in keine andere Kategorie passen. Wenn Sie mein Skript heruntergeladen und meine sp_cacheobjects-Ansicht erstellt haben, können Sie Folgendes ausführen. Jede Version der AdventureWorks-Datenbank sollte funktionieren. Dieses Skript erstellt eine Kopie einer Tabelle und erstellt ein paar eindeutige Indizes darauf. Es massiert auch den Zwischensummenbetrag, um alle Dezimalstellen zu entfernen.

USE AdventureWorks2016;
GO
DROP TABLE IF EXISTS newsales;
GO
-- Make a copy of the Sales.SalesOrderHeader table
SELECT * INTO dbo.newsales
FROM Sales.SalesOrderHeader;
GO
UPDATE dbo.newsales
SET SubTotal = cast(cast(SubTotal as int) as money);
GO
CREATE UNIQUE index newsales_ident
    ON newsales(SalesOrderID);
GO
CREATE INDEX IX_Sales_SubTotal ON newsales(SubTotal);
GO
-- Adhoc query plan reuse
DBCC FREEPROCCACHE;
GO
-- adhoc query
SELECT * FROM dbo.newsales
WHERE SubTotal = 4;
GO
SELECT * FROM sp_cacheobjects;
GO

In meiner Ausgabe sehen Sie zwei Adhoc-Pläne. Einer ist für die SELECT-Anweisung aus den newsales Tabelle, und die andere ist für das SELECT aus meinen sp_cacheobjects Aussicht. Da der Plan zwischengespeichert wird, kann derselbe Plan wiederverwendet werden, wenn EXAKT dieselbe Abfrage erneut ausgeführt wird, und Sie sehen die Usecounts Wertsteigerung. Allerdings gibt es einen Haken. Damit ein Adhoc-Plan wiederverwendet werden kann, muss der SQL-String absolut identisch sein. Wenn Sie Zeichen in der SQL ändern, wird die Abfrage nicht als dieselbe Abfrage erkannt und ein neuer Plan generiert. Wenn ich sogar ein Leerzeichen hinzufüge, den Kommentar oder einen neuen Zeilenumbruch einfüge, ist es nicht dieselbe Zeichenfolge. Wenn ich die Groß-/Kleinschreibung ändere, bedeutet das, dass es unterschiedliche ASCII-Codewerte gibt, also nicht dieselbe Zeichenfolge.

Sie können dies selbst ausprobieren, indem Sie verschiedene Variationen meiner ersten SELECT-Anweisung aus den newsales ausführen Tisch. Sie sehen für jeden eine andere Zeile im Cache. Nachdem ich mehrere Variationen ausgeführt habe – Ändern der gesuchten Nummer, Ändern der Groß-/Kleinschreibung, Hinzufügen des Kommentars und einer neuen Zeile – sehe ich Folgendes im Cache. Das SELECT wird aus meiner Sicht wiederverwendet, aber alles andere hat einen usecounts Wert 1.

Eine zusätzliche Anforderung für die Wiederverwendung von Adhoc-Abfrageplänen besteht darin, dass die Sitzung, in der die Abfrage ausgeführt wird, dieselben SET-Optionen in Kraft haben muss . Es gibt eine weitere Spalte in der Ausgabe, die Sie rechts neben dem Abfragetext sehen können, namens SETOPTS. Dies ist eine Bitfolge mit einem Bit für jede relevante SET-Option. Wenn Sie eine der Optionen ändern, z. B. SET ANSI_NULLS OFF, ändert sich die Bitzeichenfolge, und derselbe Plan mit der ursprünglichen Bitzeichenfolge kann nicht wiederverwendet werden.

Vorbereitete zusammengestellte Pläne

Der zweite Typ eines zwischengespeicherten kompilierten Plans ist ein VORBEREITETER Plan. Wenn Ihre Anfrage bestimmte Anforderungen erfüllt. Es kann tatsächlich automatisch parametriert werden. Es wird in den Metadaten als PREPARED angezeigt und die SQL-Zeichenfolge zeigt eine Parametermarkierung. Hier ist ein Beispiel:

Der PREPARED-Plan zeigt die Parametermarkierung als @1 und enthält nicht den tatsächlichen Wert. Beachten Sie, dass es eine Zeile für eine ADHOC-Abfrage mit einem tatsächlichen Wert von 5555 gibt, aber das ist eigentlich nur eine „Hülle“ der echten Abfrage. Es speichert nicht den gesamten Plan, sondern nur die Abfrage und einige identifizierende Details, um dem Abfrageprozessor zu helfen, den parametrisierten Plan im Cache zu finden. Beachten Sie die Größe (verwendete Seiten ) ist viel kleiner als der PREPARED-Plan.

Der Standard-Parametrierungsmodus, genannt EINFACHE Parametrisierung, ist äußerst streng in Bezug darauf, welche Pläne parametrisiert werden können. Es sind wirklich nur die einfachsten Abfragen, die standardmäßig parametrierbar sind. Abfragen, die JOIN, GROUP BY, OR und viele andere relativ häufige Abfragekonstrukte enthalten, verhindern, dass eine Abfrage parametrisiert wird. Abgesehen davon, dass keines dieser Konstrukte vorhanden ist, ist das Wichtigste für die SIMPLE-Parametrisierung, dass die Abfrage SICHER ist. Dies bedeutet, dass es nur einen möglichen Plan gibt, unabhängig davon, welche Werte für Parameter übergeben werden. (Natürlich könnte auch eine Abfrage ohne Parameter SICHER sein.) Meine Abfrage sucht nach einer genauen Übereinstimmung in der Spalte SalesOrderID , die über einen eindeutigen Index verfügt. Der vorhandene Nonclustered-Index könnte also verwendet werden, um jede passende Zeile zu finden. Egal welchen Wert ich verwende, 55555 oder etwas anderes, es wird nie mehr als eine Zeile geben, was bedeutet, dass der Plan immer noch gut ist.

In meinem Ad-hoc-Abfrageplanbeispiel suchte ich nach übereinstimmenden Werten für SubTotal . Einige Zwischensummen Werte kommen einige Male oder gar nicht vor, daher wäre ein Nonclustered-Index gut. Andere Werte könnten jedoch viele Male vorkommen, sodass der Index NICHT nützlich wäre. Daher ist der Abfrageplan nicht SICHER und die Abfrage kann nicht parametrisiert werden. Deshalb haben wir für mein erstes Beispiel einen Adhoc-Plan gesehen.

WENN Sie Abfragen mit JOIN oder anderen unzulässigen Konstrukten haben, können Sie SQL Server anweisen, bei der Parametrisierung aggressiver vorzugehen, indem Sie eine Datenbankoption ändern:

ALTER DATABASE AdventureWorks2016 SET parameterization FORCED;
GO

Wenn Sie Ihre Datenbank auf FORCED-Parametrisierung einstellen, bedeutet dies, dass SQL Server eine ganze Menge mehr Abfragen parametrisiert, einschließlich solcher mit JOIN, GROUP BY, OR usw. Dies bedeutet jedoch auch, dass SQL Server eine Abfrage parametrisieren kann, die nicht SICHER ist. Es kann einen Plan entwickeln, der gut ist, wenn nur wenige Zeilen zurückgegeben werden, und den Plan dann wiederverwenden, wenn viele Zeilen zurückgegeben werden. Dies kann zu einer sehr suboptimalen Leistung führen.

Eine letzte Option für einen vorbereiteten Plan ist die explizite Vorbereitung eines Plans. Dieses Verhalten wird normalerweise durch eine Anwendung mit SQLPrepare aufgerufen und SQLExecute APIs. Sie geben die Abfrage mit Parametermarkierungen an, Sie geben die Datentypen an und Sie geben die zu verwendenden spezifischen Werte an. Dieselbe Abfrage kann dann mit anderen spezifischen Werten erneut ausgeführt werden, und der vorhandene Plan wird verwendet. Obwohl die Verwendung explizit vorbereiteter Pläne für die Fälle möglich ist, in denen SQL Server nicht parametrisiert und Sie dies wünschen, hindert dies SQL Server nicht daran, einen Plan zu verwenden, der NICHT für nachfolgende Parameter geeignet ist. Sie müssen Ihre Abfragen mit vielen verschiedenen Eingabewerten testen und sicherstellen, dass Sie die erwartete Leistung erhalten, wenn ein Plan wiederverwendet wird.

Die Metadaten (z. B. meine sp_cacheobjects view) zeigt nur PREPARED für alle drei Arten von Plänen:FORCED und SIMPLE Autoparametrierung und EXPLICIT Parametrierung.

Kompilierte Pläne verarbeiten

Der letzte objtype Der Wert für kompilierte Pläne gilt für eine gespeicherte Prozedur, die als Proc angezeigt wird. Wenn möglich, sind gespeicherte Prozeduren aufgrund ihrer einfachen Verwaltung vom Server selbst die beste Wahl für wiederverwendbaren Code, aber das bedeutet nicht, dass sie immer die beste Leistung bieten. Genau wie die FORCED-Parametrisierungsoption (und auch die explizite Parametrisierung) verwenden Stored Procedures „Parameter Sniffing“. Das heißt, der erste übergebene Parameterwert bestimmt den Plan. Wenn nachfolgende Ausführungen mit demselben Plan gut funktionieren, ist das Parameter-Sniffing kein Problem und kann sogar von Vorteil sein, da es uns die Kosten für die Neukompilierung und Neuoptimierung erspart. Wenn jedoch nachfolgende Ausführungen mit unterschiedlichen Werten nicht den ursprünglichen Plan verwenden sollten, haben wir ein Problem. Ich zeige Ihnen ein Beispiel für Parameter-Sniffing, das ein Problem verursacht

Ich erstelle eine gespeicherte Prozedur basierend auf den newsales Tabelle, die wir zuvor verwendet haben. Die Prozedur hat eine einzige Abfrage, die basierend auf der SalesOrderID filtert -Spalte, auf der wir einen Nonclustered-Index erstellt haben. Die Abfrage basiert auf einer Ungleichheit, sodass die Abfrage für einige Werte nur wenige Zeilen zurückgeben und den Index verwenden kann, und für andere Werte kann die Abfrage VIELE Zeilen zurückgeben. Mit anderen Worten, die Abfrage ist nicht SICHER.

USE AdventureWorks2016;
GO
DROP PROC IF EXISTS get_sales_range;
GO
CREATE PROC get_sales_range
   @num int
AS
    SELECT * FROM dbo.newsales
    WHERE SalesOrderID < @num;
GO

Ich verwende die Option SET STATISTICS IO ON, um zu sehen, wie viel Arbeit geleistet wird, wenn die Prozedur ausgeführt wird. Zuerst führe ich es mit einem Parameter aus, der nur ein paar Zeilen zurückgibt:

SET STATISTICS IO ON
GO
EXEC get_sales_range 43700;
GO

Der STATISTICS IO-Wert gibt an, dass 43 logische Lesevorgänge erforderlich waren, um 41 Zeilen zurückzugeben. Dies ist für einen nicht gruppierten Index normal. Jetzt führen wir die Prozedur noch einmal mit einem viel größeren Wert aus.

EXEC get_sales_range 66666;
GO
SELECT * FROM sp_cacheobjects;
GO
This time, we see that SQL Server used a whole lot more reads:

Eigentlich ein Tabellen-Scan der newsales table benötigt nur 843 Lesevorgänge, dies ist also eine weitaus schlechtere Leistung als ein Tabellenscan. Die sp_cacheobjects Ansicht zeigt uns, dass der PROC-Plan für diese zweite Ausführung wiederverwendet wurde. Dies ist ein Beispiel dafür, wann Parameter-Sniffing NICHT gut ist.

Was können wir also tun, wenn Parameter-Sniffing ein Problem darstellt? Im nächsten Beitrag werde ich Ihnen mitteilen, wann SQL Server einen neuen Plan entwickelt und alte nicht wiederverwendet. Wir sehen uns an, wie Sie eine Neukompilierung erzwingen (oder fördern) können, und wir werden auch sehen, wann SQL Server Ihre Abfragen automatisch neu kompiliert.

Spotlight Cloud kann Ihre Leistungsüberwachung und SQL-Server-Diagnose revolutionieren. Beginnen Sie mit Ihrer kostenlosen Testversion über den folgenden Link: