Database
 sql >> Datenbank >  >> RDS >> Database

Einfache Parametrisierung und triviale Pläne – Teil 1

Dies ist der erste Teil einer Serie über einfache Parametrierung und triviale Pläne . Diese beiden Kompilierungsfunktionen sind eng miteinander verbunden und haben ähnliche Ziele. Sowohl die Leistung als auch die Effizienz für Workloads, bei denen häufig einfache Anweisungen übermittelt werden.

Trotz der „einfachen“ und „trivialen“ Namen haben beide subtile Verhaltensweisen und Implementierungsdetails, die es schwierig machen können, ihre Funktionsweise zu verstehen. Diese Serie hält sich nicht zu lange mit den Grundlagen auf, sondern konzentriert sich auf weniger bekannte Aspekte, die wahrscheinlich selbst die erfahrensten Datenbankprofis stolpern lassen.

In diesem ersten Teil betrachte ich nach einer kurzen Einführung die Auswirkungen der einfachen Parametrisierung im Plan-Cache.

Einfache Parametrierung

Es ist fast immer besser, explizit zu parametrisieren Anweisungen, anstatt sich darauf zu verlassen, dass der Server dies tut. Wenn Sie explizit sind, haben Sie die vollständige Kontrolle über alle Aspekte des Parametrisierungsprozesses, einschließlich wo Parameter verwendet werden, die genauen verwendeten Datentypen und wann Pläne wiederverwendet werden.

Die meisten Clients und Treiber bieten spezifische Möglichkeiten zur Verwendung der expliziten Parametrisierung. Es gibt auch Optionen wie sp_executesql , gespeicherte Prozeduren und Funktionen.

Ich werde nicht auf die verwandten Probleme des Parameter-Sniffing oder der SQL-Injektion eingehen, da sie zwar wichtig sind, aber nicht im Mittelpunkt dieser Serie stehen. Dennoch sollten Sie beim Schreiben von Code beides im Hinterkopf behalten.

Für Legacy-Anwendungen oder anderen Code von Drittanbietern, der nicht einfach geändert werden kann, ist eine explizite Parametrisierung möglicherweise nicht immer möglich. Möglicherweise können Sie einige Hindernisse mithilfe von Vorlagenplananleitungen überwinden. Auf jeden Fall wäre es eine ungewöhnliche Arbeitslast, die serverseitig nicht mindestens einige parametrisierte Anweisungen enthält.

Shell-Pläne

Als SQL Server 2005 Erzwungene Parametrisierung einführte , die vorhandene Auto-Parametrisierung Die Funktion wurde in Einfache Parametrierung umbenannt . Trotz Begriffsänderung einfache Parametrierung funktioniert genauso wie Auto-Parametrisierung hat es immer getan:SQL Server versucht, konstante Literalwerte in Ad-hoc-Anweisungen durch Parametermarkierungen zu ersetzen. Das Ziel besteht darin, Kompilierungen zu reduzieren, indem die Wiederverwendung von zwischengespeicherten Plänen erhöht wird.

Sehen wir uns ein Beispiel an, das die Stack Overflow 2010-Datenbank auf SQL Server 2019 CU 14 verwendet. Die Datenbankkompatibilität ist auf 150 festgelegt, und der Kostenschwellenwert für Parallelität ist auf 50 festgelegt, um Parallelität im Moment zu vermeiden:

EXECUTE sys.sp_configure
    @configname = 'show advanced options',
    @configvalue = 1;
RECONFIGURE;
GO
EXECUTE sys.sp_configure
    @configname = 'cost threshold for parallelism',
    @configvalue = 50;
RECONFIGURE;

Beispielcode:

-- Clear the cache of plans for this database
ALTER DATABASE SCOPED CONFIGURATION 
    CLEAR PROCEDURE_CACHE;
GO
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 2521;
GO
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 2827;
GO
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 3144;
GO
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 3151;
GO

Diese Anweisungen enthalten Prädikate, die sich nur in ihren konstanten Literalwerten unterscheiden. SQL Server wendet erfolgreich die einfache Parametrisierung an , was zu einem parametrisierten Plan führt. Der einzelne parametrisierte Plan wird viermal verwendet, wie wir durch Abfragen des Plan-Cache sehen können:

SELECT
    CP.usecounts,
    CP.cacheobjtype,
    CP.objtype,
    CP.size_in_bytes,
    ST.[text],
    QP.query_plan
FROM sys.dm_exec_cached_plans AS CP
OUTER APPLY sys.dm_exec_sql_text (CP.plan_handle) AS ST
OUTER APPLY sys.dm_exec_query_plan (CP.plan_handle) AS QP
WHERE 
    ST.[text] NOT LIKE '%dm_exec_cached_plans%'
    AND ST.[text] LIKE '%DisplayName%Users%'
ORDER BY 
    CP.usecounts ASC;

Die Ergebnisse zeigen ein Adhoc Plan-Cache-Eintrag für jede ursprüngliche Anweisung und ein einzelnes Prepared planen:

Vier Ad-hoc-Pläne und ein vorbereiteter Plan

A Vorbereitet -Anweisung ähnelt einer gespeicherten Prozedur, wobei Parameter aus Literalwerten abgeleitet werden, die in Adhoc gefunden werden Erklärung. Ich erwähne dies als nützliches mentales Modell, wenn ich über den serverseitigen Parametrisierungsprozess nachdenke.

Beachten Sie, dass SQL Server beide zwischenspeichert dem Originaltext und der parametrisierten Form. Wenn die einfache Parametrisierung erfolgreich ist, ist der mit dem Originaltext verknüpfte Plan Adhoc und enthält keinen vollständigen Ausführungsplan. Stattdessen ist der zwischengespeicherte Plan eine Shell mit sehr wenig außer einem Zeiger auf Prepared parametrisierter Plan.

Die XML-Darstellung der Shell-Pläne Text enthalten wie:

<ShowPlanXML xmlns="http://schemas.microsoft.com/sqlserver/2004/07/showplan" Version="1.539" Build="15.0.4188.2">
<BatchSequence>
<Batch>
<Statements>
<StmtSimple 
  StatementText="SELECT U.DisplayName&#xD;&#xA;FROM dbo.Users AS U &#xD;&#xA;WHERE U.Reputation = 3151"
  StatementId="1" 
  StatementCompId="1" 
  StatementType="SELECT" 
  RetrievedFromCache="true" 
  ParameterizedPlanHandle="0x0600050090C8321CE04B4B079E01000001000000000000000000000000000000000000000000000000000000" 
  ParameterizedText="(@1 smallint)SELECT [U].[DisplayName] FROM [dbo].[Users] [U] WHERE [U].[Reputation]=@1" />
</Statements>
</Batch>
</BatchSequence>
</ShowPlanXML>

Das ist der gesamte Plan. Das ParameterizedPlanHandle Punkte aus dem Adhoc Shell zum vollständig parametrisierten Plan. Der Handle-Wert ist für alle vier Shell-Pläne gleich.

Stubs planen

Shell-Pläne sind kleiner als ein vollständig kompilierter Plan – im Beispiel 16 KB statt 40 KB. Dies kann immer noch zu einer beträchtlichen Speichermenge führen, wenn Sie viele Anweisungen mit einfacher Parametrisierung oder vielen verschiedenen Parameterwerten haben. Die meisten SQL Server-Instanzen sind nicht so voll mit Arbeitsspeicher, dass sie es sich leisten können, ihn auf diese Weise zu verschwenden. Die Shell-Pläne werden von SQL Server als sehr entbehrlich angesehen, aber sie zu finden und zu entfernen, verbraucht Ressourcen und kann zu einem Streitpunkt werden.

Wir können den Gesamtspeicherverbrauch für Shell-Pläne reduzieren, indem wir die Option „Optimieren für Ad-hoc-Workloads“ aktivieren.

EXECUTE sys.sp_configure
    @configname = 'show advanced options',
    @configvalue = 1;
RECONFIGURE;
GO
EXECUTE sys.sp_configure
    @configname = 'optimize for ad hoc workloads',
    @configvalue = 1;
RECONFIGURE;

Dadurch wird ein winziger Stub zwischengespeichert, wenn anstelle einer Shell zum ersten Mal auf eine Ad-hoc-Anweisung gestoßen wird. Der Stub dient als Lesezeichen, damit sich der Server daran erinnern kann, dass er den genauen Anweisungstext schon einmal gesehen hat. Wenn derselbe Text ein zweites Mal vorkommt, werden Kompilierung und Caching so fortgesetzt, als würden sie für Ad-hoc-Workloads optimieren wurden nicht aktiviert.

Erneutes Ausführen des Beispiels mit optimize for ad hoc workloads aktiviert zeigt die Auswirkung auf den Plan-Cache.

Kompilierte Plan-Stubs

Für die Ad-hoc-Anweisungen wird kein Plan zwischengespeichert, sondern nur ein Stub. Es gibt kein ParameterizedPlanHandle Zeiger auf Vorbereitet Plan, obwohl ein vollständig parametrisierter Plan ist zwischengespeichert.

Wenn Sie die Teststapel ein zweites Mal ausführen (ohne den Plan-Cache zu löschen), erhalten Sie das gleiche Ergebnis wie bei der Optimierung für Ad-hoc-Workloads wurde nicht aktiviert – vier Adhoc Shell-Pläne, die auf Prepared verweisen planen.

Bevor Sie fortfahren, setzen Sie die Optimierung für Ad-hoc-Workloads zurück auf Null setzen:

EXECUTE sys.sp_configure
    @configname = 'optimize for ad hoc workloads',
    @configvalue = 0;
RECONFIGURE;

Cache-Größenbeschränkungen planen

Unabhängig davon, ob Plan-Shells oder Plan-Stubs verwendet werden, es gibt immer noch Nachteile bei all diesen Adhoc Cache-Einträge. Ich habe bereits die Gesamtspeichernutzung erwähnt, aber jeder Plan-Cache hat auch eine maximale Anzahl von Einträgen. Auch wenn die Gesamtspeicherauslastung keine Rolle spielt, kann die schiere Menge eine Rolle spielen.

Die Grenzwerte können mit dem dokumentierten Ablaufverfolgungsflag 174 (Anzahl der Einträge) und Ablaufverfolgungsflag 8032 (Gesamtgröße) erhöht werden. Je nach Workload und anderen Speicheranforderungen ist dies möglicherweise nicht die beste Lösung. Schließlich bedeutet es nur, mehr niedrigwertige Adhoc zwischenzuspeichern Pläne, die die Erinnerung von anderen Bedürfnissen wegnehmen.

Caching nur vorbereiteter Pläne

Wenn die Workload selten Ad-hoc-Batches mit genau ausgibt derselbe Anweisungstext, das Caching von Plan-Shells oder Plan-Stubs ist eine Verschwendung von Ressourcen. Es verbraucht Speicher und kann zu Konflikten führen, wenn die SQL-Pläne Cachespeicher (CACHESTORE_SQLCP ) muss verkleinert werden, damit sie in die konfigurierten Grenzen passt.

Ideal wäre es, eingehende Ad-hoc-Batches zu parametrisieren, aber nur Zwischenspeichern der parametrisierten Version. Dies ist mit Kosten verbunden, da zukünftige Ad-hoc-Anweisungen parametrisiert werden müssen, bevor sie mit dem parametrisierten zwischengespeicherten Plan abgeglichen werden können. Andererseits wäre dies sowieso passiert, da wir bereits exakt angegeben haben Textübereinstimmungen sind für die Ziel-Workload selten.

Für Workloads, die von einfacher Parametrisierung profitieren, aber nicht vom Caching von Adhoc Einträge, gibt es ein paar Optionen.

Flag für undokumentierte Ablaufverfolgung

Die erste Option besteht darin, das undokumentierte Trace-Flag 253 zu aktivieren. Dies verhindert das Caching von Adhoc plant komplett. Es beschränkt nicht einfach die Anzahl solcher Pläne oder verhindert, dass sie im Cache „bleiben“, wie manchmal vorgeschlagen wurde.

Das Trace-Flag 253 kann auf Sitzungsebene aktiviert werden – wodurch seine Auswirkungen auf genau diese Verbindung beschränkt werden – oder allgemeiner als globales oder Start-Flag. Es fungiert auch als Abfragehinweis, dessen Verwendung jedoch eine einfache Parametrisierung verhindert, was hier kontraproduktiv wäre. Eine unvollständige Liste der Dinge, die eine einfache Parametrisierung verhindern, finden Sie im Microsoft Technical Paper, Plan Caching and Recompilation in SQL Server 2012.

Mit Trace-Flag 253 aktiv bevor der Batch kompiliert wird , nur Vorbereitet Anweisungen werden zwischengespeichert:

ALTER DATABASE SCOPED CONFIGURATION 
    CLEAR PROCEDURE_CACHE;
GO
-- Do not cache ad-hoc plans
DBCC TRACEON (253);
GO
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 2521;
GO
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 2827;
GO
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 3144;
GO
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 3151;
GO
-- Cache ad-hoc plans again
DBCC TRACEOFF (253);
GO

Die Plan-Cache-Abfrage bestätigt nur das Prepared -Anweisung wird zwischengespeichert und wiederverwendet.

Nur die vorbereitete Anweisung wird zwischengespeichert

Der nicht zwischenspeicherbare Stapel

Die zweite Option besteht darin, eine Anweisung einzuschließen, die den gesamten Stapel als nicht zwischenspeicherbar markiert . Geeignete Aussagen sind oft sicherheitsrelevant oder anderweitig sensibel.

Das mag unpraktisch klingen, aber es gibt ein paar Abschwächungen. Erstens muss die sensible Anweisung nicht ausgeführt werden – sie muss nur vorhanden sein . Wenn diese Bedingung erfüllt ist, benötigt der Benutzer, der den Stapel ausführt, nicht einmal eine Berechtigung um die sensible Anweisung auszuführen. Beachten Sie sorgfältig, dass der Effekt auf den Stapel beschränkt ist, der die vertrauliche Anweisung enthält.

Unten sind zwei angemessen vertrauliche Anweisungen und Beispielverwendungen gezeigt (wobei sich die Testanweisungen jetzt in einem einzigen Stapel befinden):

ALTER DATABASE SCOPED CONFIGURATION 
    CLEAR PROCEDURE_CACHE;
GO
-- Prevent caching of all statements in this batch.
-- Neither KEY nor CERTIFICATE need to exist.
-- No special permissions are needed.
-- GOTO is used to ensure the statements are not executed.
GOTO Start
    OPEN SYMMETRIC KEY Banana 
        DECRYPTION BY CERTIFICATE Banana;
Start:
 
/* Another way to achieve the same effect without GOTO
IF 1 = 0
BEGIN
    CREATE APPLICATION ROLE Banana 
    WITH PASSWORD = '';
END;
*/
 
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 2521;
 
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 2827;
 
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 3144;
 
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 3151;
GO

Die Vorbereitet Pläne, die durch einfache Parametrisierung erstellt wurden werden weiterhin zwischengespeichert und wiederverwendet, obwohl der übergeordnete Stapel als nicht zwischenspeicherbar markiert ist.

Nur die vorbereitete Anweisung wird zwischengespeichert

Keine Lösung ist ideal, aber bis Microsoft eine dokumentierte und unterstützte Lösung für dieses Problem bereitstellt, sind sie die besten Optionen, die mir bekannt sind.

Ende von Teil 1

Zu diesem Thema gibt es noch viel mehr zu tun. Teil zwei behandelt die Datentypen, die bei der einfachen Parametrierung zugewiesen werden beschäftigt ist.