Questa è la prima parte di una serie sulla parametrizzazione semplice e piani banali . Queste due funzionalità di compilazione sono strettamente connesse e hanno obiettivi simili. Sia le prestazioni che l'efficienza mirano ai carichi di lavoro inviando spesso semplici istruzioni.
Nonostante i nomi "semplici" e "banali", entrambi hanno comportamenti sottili e dettagli di implementazione che possono rendere difficile la comprensione del loro funzionamento. Questa serie non si sofferma troppo sulle basi, ma si concentra su aspetti meno noti che potrebbero inciampare anche i professionisti di database più esperti.
In questa prima parte, dopo una rapida introduzione, esaminerò gli effetti della semplice parametrizzazione nella cache del piano.
Parametrizzazione semplice
È quasi sempre meglio parametrizzare esplicitamente istruzioni, piuttosto che fare affidamento sul server per farlo. Essere espliciti ti dà il controllo completo su tutti gli aspetti del processo di parametrizzazione, incluso dove vengono utilizzati i parametri, i tipi di dati precisi utilizzati e quando i piani vengono riutilizzati.
La maggior parte dei client e dei driver fornisce modi specifici per utilizzare la parametrizzazione esplicita. Ci sono anche opzioni come sp_executesql
, stored procedure e funzioni.
Non entrerò nei problemi correlati dello sniffing dei parametri o dell'iniezione SQL perché, sebbene importanti, non sono al centro di questa serie. Tuttavia, dovresti scrivere il codice tenendo entrambi in primo piano nella tua mente.
Per le applicazioni legacy o altro codice di terze parti che non possono essere facilmente modificati, la parametrizzazione esplicita potrebbe non essere sempre possibile. Potresti essere in grado di superare alcuni ostacoli utilizzando le guide del piano modello. In ogni caso, si tratterebbe di un carico di lavoro insolito che non contiene almeno alcune istruzioni parametrizzate lato server.
Piani Shell
Quando SQL Server 2005 ha introdotto la parametrizzazione forzata , la parametrizzazione automatica esistente la funzione è stata rinominata in Parametrizzazione semplice . Nonostante il cambio di terminologia, semplice parametrizzazione funziona come la parametrizzazione automatica sempre fatto:SQL Server tenta di sostituire i valori letterali costanti nelle istruzioni ad hoc con indicatori di parametro. L'obiettivo è ridurre le compilazioni aumentando il riutilizzo del piano memorizzato nella cache.
Diamo un'occhiata a un esempio, utilizzando il database Stack Overflow 2010 su SQL Server 2019 CU 14. La compatibilità del database è impostata su 150 e la soglia di costo per il parallelismo è impostata su 50 per evitare il parallelismo per il momento:
EXECUTE sys.sp_configure @configname = 'show advanced options', @configvalue = 1; RECONFIGURE; GO EXECUTE sys.sp_configure @configname = 'cost threshold for parallelism', @configvalue = 50; RECONFIGURE;
Codice di esempio:
-- 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
Tali affermazioni presentano predicati che differiscono solo per i loro valori letterali costanti. SQL Server applica correttamente la parametrizzazione semplice , risultando in un piano parametrizzato. Il singolo piano parametrizzato viene utilizzato quattro volte come possiamo vedere interrogando la cache del piano:
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;
I risultati mostrano un Adhoc pianificare la voce della cache per ogni istruzione originale e un singolo Preparato piano:
Quattro piani ad hoc e un piano preparato
Un Preparato è simile a una procedura memorizzata, con parametri dedotti da valori letterali trovati in Adhoc dichiarazione. Cito questo come un modello mentale utile quando penso al processo di parametrizzazione lato server.
Si noti che SQL Server memorizza nella cache entrambi il testo originale e il modulo parametrizzato. Quando la parametrizzazione semplice ha esito positivo, il piano associato al testo originale è Adhoc e non contiene un piano di esecuzione completo. Invece, il piano memorizzato nella cache è una shell con molto poco oltre a un puntatore a Preparato piano parametrizzato.
La rappresentazione XML dei piani shell contengono testo come:
<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
FROM dbo.Users AS U 
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>
Questo è l'intero piano. La ParameterizedPlanHandle punti da Adhoc shell al piano parametrizzato completo. Il valore dell'handle è lo stesso per tutti e quattro i piani della shell.
Stub del piano
I piani Shell sono più piccoli di un piano compilato completo:16 KB invece di 40 KB nell'esempio. Ciò può comunque aggiungere una quantità significativa di memoria se si hanno molte istruzioni che utilizzano parametrizzazione semplice o molti valori di parametro diversi. La maggior parte delle istanze di SQL Server non sono così piene di memoria da potersi permettere di sprecarla in questo modo. I piani della shell sono considerati molto usa e getta da SQL Server, ma trovarli e rimuoverli consuma risorse e può diventare un punto di contesa.
Possiamo ridurre il consumo totale di memoria per i piani shell abilitando l'opzione di ottimizzazione per carichi di lavoro ad hoc.
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;
Questo memorizza nella cache un piccolo stub la prima volta che si incontra un'istruzione ad hoc invece di una shell. Lo stub funge da segnalibro in modo che il server possa ricordare di aver già visto il testo esatto dell'istruzione. Dopo aver incontrato lo stesso testo una seconda volta, la compilazione e la memorizzazione nella cache procedono come se ottimizzassero per carichi di lavoro ad hoc non erano abilitati.
Ripeti l'esempio con ottimizza per carichi di lavoro ad hoc abilitato mostra l'effetto sulla cache del piano.
Schemi di piani compilati
Nessun piano viene memorizzato nella cache per le dichiarazioni ad hoc, solo uno stub. Non esiste un ParameterizedPlanHandle puntatore a Preparato piano, sebbene un piano parametrizzato completo è memorizzato nella cache.
L'esecuzione dei batch di test una seconda volta (senza svuotare la cache del piano) fornisce lo stesso risultato di quando ottimizza per carichi di lavoro ad hoc non era abilitato:quattro Adhoc piani di shell che puntano a Preparato piano.
Prima di continuare, reimposta ottimizza per carichi di lavoro ad hoc azzerando:
EXECUTE sys.sp_configure @configname = 'optimize for ad hoc workloads', @configvalue = 0; RECONFIGURE;
Limiti delle dimensioni della cache del piano
Indipendentemente dal fatto che vengano utilizzate shell del piano o stub del piano, ci sono ancora degli svantaggi in tutti questi Adhoc voci della cache. Ho già menzionato l'utilizzo totale della memoria, ma ogni cache del piano ha anche un numero massimo di voci. Anche se l'utilizzo totale della memoria non è un problema, l'enorme quantità potrebbe esserlo.
I limiti possono essere aumentati con il flag di traccia documentato 174 (numero di voci) e il flag di traccia 8032 (dimensione totale). A seconda del carico di lavoro e di altre richieste di memoria, questa potrebbe non essere la soluzione migliore. Dopotutto, significa solo memorizzare nella cache un valore più basso Adhoc piani, sottraendo memoria ad altri bisogni.
Memorizza nella cache solo i piani preparati
Se il carico di lavoro emette raramente batch ad hoc con esattamente lo stesso testo dell'istruzione, la memorizzazione nella cache delle shell del piano o degli stub del piano è uno spreco di risorse. Consuma memoria e può causare contese durante i piani SQL archivio cache (CACHESTORE_SQLCP
) deve essere ridotto per rientrare nei limiti configurati.
L'ideale sarebbe parametrizzare i batch ad hoc in entrata, ma solo cache la versione parametrizzata. Questa operazione comporta un costo, poiché le istruzioni ad hoc future devono essere parametrizzate prima che possano essere abbinate al piano memorizzato nella cache con parametri. D'altra parte, questo sarebbe successo comunque poiché abbiamo già dichiarato esatto le corrispondenze testuali sono rare per il carico di lavoro di destinazione.
Per carichi di lavoro che beneficiano di una semplice parametrizzazione, ma non della memorizzazione nella cache di Adhoc voci, ci sono un paio di opzioni.
Bandiera di traccia non documentata
La prima opzione è abilitare il flag di traccia non documentato 253. Questo previene la memorizzazione nella cache di Adhoc piani completamente. Non limita semplicemente il numero di tali piani o impedisce loro di "rimanere" nella cache, come è stato talvolta suggerito.
Il flag di traccia 253 può essere abilitato a livello di sessione, limitando i suoi effetti solo a quella connessione, o più ampiamente come flag globale o di avvio. Funziona anche come suggerimento per la query, ma il suo utilizzo impedisce la semplice parametrizzazione, che sarebbe controproducente in questo caso. C'è un elenco parziale degli elementi che impediscono la semplice parametrizzazione nel documento tecnico Microsoft, pianificazione della memorizzazione nella cache e della ricompilazione in SQL Server 2012.
Con flag di traccia 253 attivo prima della compilazione del batch , solo il Preparato le istruzioni sono memorizzate nella cache:
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
La query della cache del piano conferma solo il Preparato l'istruzione viene memorizzata nella cache e riutilizzata.
Solo l'istruzione preparata viene memorizzata nella cache
Il lotto non memorizzabile
La seconda opzione consiste nell'includere un'istruzione che contrassegni l'intero batch come non memorizzabile nella cache . Le affermazioni appropriate sono spesso legate alla sicurezza o comunque sensibili in qualche modo.
Potrebbe sembrare impraticabile, ma ci sono un paio di mitigazioni. In primo luogo, l'istruzione sensibile non deve essere eseguita, deve solo essere presente . Quando tale condizione è soddisfatta, l'utente che esegue il batch non ha nemmeno bisogno di autorizzazione per eseguire l'istruzione sensibile. Nota attentamente, l'effetto è limitato al batch contenente l'istruzione sensibile.
Di seguito sono mostrati due istruzioni opportunamente sensibili e un esempio di utilizzo (con le istruzioni di test ora in un unico batch):
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
Il Preparato piani creati da semplice parametrizzazione sono ancora memorizzati nella cache e riutilizzati nonostante il batch padre sia contrassegnato come non memorizzabile nella cache.
Solo l'istruzione preparata viene memorizzata nella cache
Nessuna delle due soluzioni è l'ideale, ma finché Microsoft non fornisce una soluzione documentata e supportata per questo problema, sono le migliori opzioni di cui sono a conoscenza.
Fine della parte 1
C'è molto più terreno da trattare su questo argomento. La seconda parte tratterà i tipi di dati assegnati durante la parametrizzazione semplice è impiegato.