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

Parametrizzazione semplice e piani banali — Parte 3

Piani di esecuzione

È più complicato di quanto ti aspetteresti di capire dalle informazioni fornite nei piani di esecuzione se un'istruzione SQL utilizza una parametrizzazione semplice . Non sorprende che anche gli utenti di SQL Server molto esperti tendano a sbagliare, date le informazioni contraddittorie che spesso ci vengono fornite.

Diamo un'occhiata ad alcuni esempi che utilizzano il database Stack Overflow 2010 su SQL Server 2019 CU 14, con la compatibilità del database impostata su 150.

Per iniziare, avremo bisogno di un nuovo indice non cluster:

CREATE INDEX [IX dbo.Users Reputation (DisplayName)] 
ON dbo.Users (Reputation) 
INCLUDE (DisplayName);

1. Semplice parametrizzazione applicata

Questa prima query di esempio utilizza una parametrizzazione semplice :

SELECT U.DisplayName 
FROM dbo.Users AS U 
WHERE U.Reputation = 999;

La stima (pre-esecuzione) il piano ha i seguenti elementi relativi alla parametrizzazione:

Proprietà di parametrizzazione del piano stimato

Nota il @1 il parametro viene introdotto ovunque tranne il testo della query mostrato in alto.

Il effettivo (post-esecuzione) il piano prevede:

Proprietà effettive di parametrizzazione del piano

Si noti che la finestra delle proprietà ora ha perso il ParameterizedText elemento, ottenendo informazioni sul valore di runtime del parametro. Il testo della query parametrizzato viene ora mostrato nella parte superiore della finestra con "@1 ' invece di '999'.

2. Parametrizzazione semplice non applicata

Questo secondo esempio non usa la parametrizzazione semplice:

-- Projecting an extra column
SELECT 
    U.DisplayName, 
    U.CreationDate -- NEW
FROM dbo.Users AS U 
WHERE 
    U.Reputation = 999;

La stima il piano mostra:

Piano stimato non parametrizzato

Questa volta, il parametro @1 manca nella Ricerca dell'indice descrizione comando, ma il testo parametrizzato e gli altri elementi dell'elenco dei parametri sono gli stessi di prima.

Diamo un'occhiata al effettivo piano di esecuzione:

Piano effettivo non parametrizzato

I risultati sono gli stessi del precedente effettivo parametrizzato piano, ad eccezione ora della Ricerca dell'indice la descrizione comando visualizza il valore non parametrizzato '999'. Il testo della query mostrato in alto utilizza il @1 marcatore di parametro. La finestra delle proprietà usa anche @1 e visualizza il valore di runtime del parametro.

La query non è un'istruzione parametrizzata nonostante tutte le prove contrarie.

3. Parametrizzazione non riuscita

Anche il mio terzo esempio è non parametrizzato dal server:

-- LOWER function used
SELECT 
    U.DisplayName, 
    LOWER(U.DisplayName)
FROM dbo.Users AS U 
WHERE 
    U.Reputation = 999;

La stima il piano è:

Parametrizzazione del piano stimata non riuscita

Non si fa menzione di un @1 parametro ovunque ora e l'Elenco parametri manca una sezione della finestra delle proprietà.

Il effettivo il piano di esecuzione è lo stesso, quindi non mi preoccuperò di mostrarlo.

4. Piano parametrizzato parallelo

Voglio mostrarti un altro esempio usando il parallelismo nel piano di esecuzione. Il basso costo stimato delle mie query di test significa che dobbiamo abbassare la soglia di costo per il parallelismo a 1:

EXECUTE sys.sp_configure
    @configname = 'cost threshold for parallelism',
    @configvalue = 1;
RECONFIGURE;

L'esempio è un po' più complesso questa volta:

SELECT 
    U.DisplayName 
FROM dbo.Users AS U 
WHERE 
    U.Reputation >= 5 
    AND U.DisplayName > N'ZZZ' 
ORDER BY 
    U.Reputation DESC;

La stima piano di esecuzione è:

Piano con parametri paralleli stimato

Il testo della query nella parte superiore rimane senza parametri mentre tutto il resto lo è. Ci sono due indicatori di parametro ora, @1 e @2 , perché semplice parametrizzazione trovato due valori letterali adatti.

Il effettivo il piano di esecuzione segue lo schema dell'esempio 1:

Piano parametrizzato parallelo effettivo

Il testo della query nella parte superiore è ora parametrizzato e la finestra delle proprietà contiene i valori dei parametri di runtime. Questo piano parallelo (con un Ordina operatore) è definitivamente parametrizzato dal server utilizzando la parametrizzazione semplice .

Metodi affidabili

Ci sono ragioni per tutti i comportamenti mostrati finora, e alcuni altri in più. Cercherò di spiegare molti di questi nella prossima parte di questa serie quando tratterò la compilazione del piano.

Nel frattempo, la situazione con showplan in generale, e SSMS in particolare, è tutt'altro che ideale. È fonte di confusione per le persone che hanno lavorato con SQL Server per tutta la loro carriera. Di quali indicatori di parametro ti fidi e quali ignori?

Esistono diversi metodi affidabili per determinare se a una particolare istruzione è stata applicata con successo o meno una parametrizzazione semplice.

Archivio query

Inizierò con uno dei più convenienti, il negozio di query. Sfortunatamente, non è sempre così semplice come potresti immaginare.

Devi abilitare la funzione di archivio query per il contesto database dove viene eseguita l'istruzione e il OPERATION_MODE deve essere impostato su READ_WRITE , consentendo all'archivio query di raccogliere attivamente i dati.

Dopo aver soddisfatto queste condizioni, l'output dello showplan successivo all'esecuzione contiene attributi aggiuntivi, tra cui StatementParameterizationType . Come suggerisce il nome, contiene un codice che descrive il tipo di parametrizzazione utilizzata per l'istruzione.

È visibile nella finestra delle proprietà di SSMS quando viene selezionato il nodo principale di un piano:

StatementParameterizationType

I valori sono documentati in sys.query_store_query :

  • 0 – Nessuno
  • 1 – Utente (parametrizzazione esplicita)
  • 2 – Parametrizzazione semplice
  • 3 – Parametrizzazione forzata

Questo attributo vantaggioso viene visualizzato in SSMS solo quando è effettivo il piano è richiesto e manca quando è stimato il piano è selezionato. È importante ricordare che il piano deve essere memorizzato nella cache . Richiesta di un stima piano da SSMS non memorizza nella cache il piano prodotto (da SQL Server 2012).

Una volta che il piano è stato memorizzato nella cache, StatementParameterizationType appare nei soliti posti, anche tramite sys.dm_exec_query_plan .

Puoi anche fidarti degli altri luoghi in cui il tipo di parametrizzazione è registrato nell'archivio query, come query_parameterization_type_desc colonna in sys.query_store_query .

Un avvertimento importante. Quando la query memorizza OPERATION_MODE è impostato su READ_ONLY , il StatementParameterizationType l'attributo è ancora popolato in SSMS effettivo piani, ma è sempre zero —dando una falsa impressione che l'istruzione non fosse parametrizzata quando avrebbe potuto benissimo esserlo.

Se sei felice di abilitare l'archivio query, sei sicuro che sia in lettura e scrittura e guardi solo i piani post-esecuzione in SSMS, questo funzionerà per te.

Predicati del piano standard

Il testo della query mostrato nella parte superiore della finestra dello showplan grafico in SSMS non è affidabile, come hanno mostrato gli esempi. Né puoi fare affidamento su ParameterList visualizzato nelle Proprietà finestra quando viene selezionato il nodo radice del piano. Il testo parametrizzato attributo mostrato per stimato anche solo i piani non sono conclusivi.

Puoi, tuttavia, fare affidamento sulle proprietà associate ai singoli operatori del piano. Gli esempi forniti mostrano che sono presenti nelle descrizioni comandi quando si passa sopra un operatore.

Un predicato contenente un indicatore di parametro come @1 o @2 indica un piano parametrizzato. Gli operatori con maggiori probabilità di contenere un parametro sono Scansione indice , Ricerca indice e Filtro .

Predicati con indicatori di parametro

Se la numerazione inizia con @1 , utilizza una parametrizzazione semplice . La parametrizzazione forzata inizia con @0 . Devo menzionare che lo schema di numerazione qui documentato è soggetto a modifiche in qualsiasi momento:

Avviso di modifica

Tuttavia, questo è il metodo che utilizzo il più delle volte per determinare se un piano era soggetto a parametrizzazione lato server. In genere è facile e veloce controllare visivamente un piano per i predicati contenenti indicatori di parametro. Questo metodo funziona anche per entrambi i tipi di piani, stimati e effettivo .

Oggetti di gestione dinamica

Esistono diversi modi per eseguire query sulla cache del piano e sui DMO correlati per determinare se un'istruzione è stata parametrizzata. Naturalmente, queste query funzionano solo sui piani in cache, quindi l'istruzione deve essere stata eseguita fino al completamento, memorizzata nella cache e non successivamente eliminata per nessun motivo.

L'approccio più diretto è cercare un Adhoc pianificare utilizzando una corrispondenza testuale SQL esatta alla dichiarazione di interesse. Il ad hoc il piano sarà una shell contenente un ParameterizedPlanHandle se l'istruzione è parametrizzata dal server. L'handle del piano viene quindi utilizzato per individuare il Preparato Piano. Un ad hoc piano non esisterà se l'ottimizzazione per carichi di lavoro ad hoc è abilitata e l'istruzione in questione è stata eseguita una sola volta.

Questo tipo di richiesta spesso finisce per distruggere una quantità significativa di XML ed eseguire la scansione dell'intera cache del piano almeno una volta. È anche facile sbagliare il codice, anche perché i piani nella cache coprono un intero batch. Un batch può contenere più istruzioni, ognuna delle quali può essere parametrizzata o meno. Non tutti i DMO funzionano con la stessa granularità (batch o dichiarazione), rendendo abbastanza facile sbloccarsi.

Di seguito è mostrato un modo efficiente per elencare le dichiarazioni di interesse, insieme ai frammenti di piano solo per quelle singole dichiarazioni:

SELECT
    StatementText =
        SUBSTRING(T.[text], 
            1 + (QS.statement_start_offset / 2), 
            1 + ((QS.statement_end_offset - 
                QS.statement_start_offset) / 2)),
    IsParameterized = 
        IIF(T.[text] LIKE N'(%',
            'Yes',
            'No'),
    query_plan = 
        TRY_CONVERT(xml, P.query_plan)
FROM sys.dm_exec_query_stats AS QS
CROSS APPLY sys.dm_exec_sql_text (QS.[sql_handle]) AS T
CROSS APPLY sys.dm_exec_text_query_plan (
    QS.plan_handle, 
    QS.statement_start_offset, 
    QS.statement_end_offset) AS P
WHERE 
    -- Statements of interest
    T.[text] LIKE N'%DisplayName%Users%'
    -- Exclude queries like this one
    AND T.[text] NOT LIKE N'%sys.dm%'
ORDER BY
    QS.last_execution_time ASC,
    QS.statement_start_offset ASC;

Per illustrare, eseguiamo un singolo batch contenente i quattro esempi precedenti:

ALTER DATABASE SCOPED CONFIGURATION 
    CLEAR PROCEDURE_CACHE;
GO
-- Example 1
SELECT U.DisplayName 
FROM dbo.Users AS U 
WHERE U.Reputation = 999;
 
-- Example 2
SELECT 
    U.DisplayName, 
    U.CreationDate 
FROM dbo.Users AS U 
WHERE 
    U.Reputation = 999;
 
-- Example 3
SELECT 
    U.DisplayName, 
    LOWER(U.DisplayName)
FROM dbo.Users AS U 
WHERE 
    U.Reputation = 999;
 
-- Example 4
SELECT 
    U.DisplayName 
FROM dbo.Users AS U 
WHERE 
    U.Reputation >= 5 
    AND U.DisplayName > N'ZZZ' 
ORDER BY 
    U.Reputation DESC;
GO

L'output della query DMO è:

Output query DMO

Ciò conferma che solo gli esempi 1 e 4 sono stati parametrizzati correttamente.

Contatori delle prestazioni

È possibile utilizzare i contatori delle prestazioni di SQL Statistics per ottenere una visione dettagliata dell'attività di parametrizzazione per entrambi i stimati e effettivo piani. I contatori utilizzati non hanno l'ambito per sessione, quindi dovrai utilizzare un'istanza di test senza altre attività simultanee per ottenere risultati accurati.

Integrerò le informazioni del contatore di parametrizzazione con i dati di sys.dm_exec_query_optimizer_info DMO per fornire statistiche anche su piani banali.

È necessaria una certa attenzione per evitare che le dichiarazioni che leggono le informazioni sui contatori modifichino tali contatori stessi. Affronterò questo problema creando un paio di stored procedure temporanee:

CREATE PROCEDURE #TrivialPlans
AS
SET NOCOUNT ON;
 
SELECT
    OI.[counter],
    OI.occurrence
FROM sys.dm_exec_query_optimizer_info AS OI
WHERE
    OI.[counter] = N'trivial plan';
GO
CREATE PROCEDURE #PerfCounters
AS
SET NOCOUNT ON;
 
SELECT
    PC.[object_name],
    PC.counter_name,
    PC.cntr_value
FROM 
    sys.dm_os_performance_counters AS PC
WHERE 
    PC.counter_name LIKE N'%Param%';

Lo script per testare una particolare istruzione sarà quindi simile a questo:

ALTER DATABASE SCOPED CONFIGURATION 
    CLEAR PROCEDURE_CACHE;
GO
EXECUTE #PerfCounters;
EXECUTE #TrivialPlans;
GO
SET SHOWPLAN_XML ON;
GO
-- The statement(s) under test:
-- Example 3
SELECT 
    U.DisplayName, 
    LOWER(U.DisplayName)
FROM dbo.Users AS U 
WHERE 
    U.Reputation = 999;
GO
SET SHOWPLAN_XML OFF;
GO
EXECUTE #TrivialPlans;
EXECUTE #PerfCounters;

Commenta il SHOWPLAN_XML batch out per eseguire le istruzioni di destinazione e ottenere effettivi piani. Lasciali in posizione per stima piani di esecuzione.

L'esecuzione del tutto come scritto dà i seguenti risultati:

Risultati del test del contatore delle prestazioni

Ho evidenziato sopra dove i valori sono cambiati durante il test dell'esempio 3.

L'aumento del contatore "piano banale" da 1050 a 1051 mostra che è stato trovato un piano banale per la dichiarazione di prova.

I contatori di parametrizzazione semplice sono aumentati di 1 sia per i tentativi che per gli errori, mostrando che SQL Server ha tentato di parametrizzare l'istruzione, ma non è riuscito.

Fine della parte 3

Nella prossima parte di questa serie, spiegherò le cose curiose che abbiamo visto descrivendo come la semplice parametrizzazione e piani banali interagire con il processo di compilazione.

Se hai modificato la tua soglia di costo per il parallelismo per eseguire gli esempi, ricordati di reimpostarlo (il mio era impostato a 50):

EXECUTE sys.sp_configure
    @configname = 'cost threshold for parallelism',
    @configvalue = 50;
RECONFIGURE;