Non fraintendetemi:adoro la proprietà Actual Rows Read che abbiamo visto arrivare nei piani di esecuzione di SQL Server alla fine del 2015. Ma in SQL Server 2016 SP1, meno di due mesi fa (e considerando che abbiamo trascorso il Natale nel mezzo, non credo che il tempo trascorso da allora conti molto), abbiamo ottenuto un'altra eccitante aggiunta – Numero stimato di righe da leggere (oh, e questo è in qualche modo dovuto all'elemento Connect che ho inviato, dimostrando sia che vale la pena inviare gli elementi Connect e rendendo questo post idoneo per il martedì T-SQL di questo mese, ospitato da Brent Ozar (@brento) sull'argomento degli elementi Connect ).
Ricapitoliamo un momento... quando SQL Engine accede ai dati in una tabella, utilizza un'operazione di scansione o un'operazione di ricerca. E a meno che la ricerca non abbia un predicato di ricerca che può accedere al massimo a una riga (perché sta cercando una corrispondenza di uguaglianza su un insieme di colonne - potrebbe essere solo una singola colonna - che sono note per essere uniche), allora la ricerca eseguirà un RangeScan, e si comporta proprio come una Scansione, proprio attraverso il sottoinsieme di righe che sono soddisfatte da Seek Predicate.
Le righe soddisfatte da un predicato di ricerca (nel caso di RangeScan di un'operazione di ricerca) o tutte le righe della tabella (nel caso di un'operazione di scansione) vengono trattate essenzialmente allo stesso modo. Entrambi potrebbero essere terminati in anticipo se non vengono richieste più righe dall'operatore alla sua sinistra, ad esempio se un operatore Top da qualche parte ha già acquisito abbastanza righe o se un Merge Operator non ha più righe con cui confrontare. Ed entrambi potrebbero essere ulteriormente filtrati da un predicato residuo (mostrato come la proprietà "Predicato") prima ancora che le righe vengano servite dall'operatore Scan/Seek. Le proprietà "Numero di righe" e "Numero stimato di righe" ci direbbero quante righe dovrebbero essere prodotte dall'operatore, ma non avevamo alcuna informazione su come potrebbero essere filtrate le righe solo dal predicato di ricerca. Potevamo vedere la TableCardinality, ma questo era davvero utile solo per gli operatori di Scan, dove c'era la possibilità che Scan potesse cercare nell'intera tabella le righe di cui aveva bisogno. Non è stato affatto utile per Seeks.
La query che sto eseguendo qui è sul database WideWorldImporters ed è:
SELECT COUNT(*) FROM Sales.Orders WHERE SalespersonPersonID = 7 AND YEAR(OrderDate) = 2013 AND MONTH(OrderDate) = 4;
Inoltre, ho un indice in gioco:
CREATE NONCLUSTERED INDEX rf_Orders_SalesPeople_OrderDate ON Sales.Orders (SalespersonPersonID, OrderDate);
Questo indice copre - la query non ha bisogno di altre colonne per ottenere la sua risposta - ed è stato progettato in modo che un predicato di ricerca possa essere utilizzato su SalespersonPersonID, filtrando rapidamente i dati fino a un intervallo più piccolo. Le funzioni su OrderDate significano che quegli ultimi due predicati non possono essere utilizzati all'interno del Seek Predicate, quindi sono invece relegati al Residual Predicate. Una query migliore filtrerebbe quelle date usando OrderDate>='20130401' AND OrderDate <'20130501', ma sto immaginando uno scenario qui che è fin troppo comune...
Ora, se eseguo la query, posso vedere l'impatto dei predicati residui. Plan Explorer fornisce anche quell'utile avviso di cui avevo scritto prima.
Riesco a vedere molto chiaramente che il RangeScan è 7.276 righe e che il predicato residuo lo filtra fino a 149. Plan Explorer mostra maggiori informazioni su questo nella descrizione comando:
Ma senza eseguire la query, non riesco a vedere queste informazioni. Semplicemente non è lì. Le proprietà nel piano stimato non ce l'hanno:
E sono sicuro di non aver bisogno di ricordartelo:queste informazioni non sono nemmeno presenti nella cache del piano. Dopo aver preso il piano dalla cache usando:
SELECT p.query_plan, t.text FROM sys.dm_exec_cached_plans c CROSS APPLY sys.dm_exec_query_plan(c.plan_handle) p CROSS APPLY sys.dm_exec_sql_text(c.plan_handle) t WHERE t.text LIKE '%YEAR%';
L'ho aperto e, sicuramente, nessun segno di quel valore di 7.276. Ha lo stesso aspetto del piano stimato che ho appena mostrato.
Ottenere i piani dalla cache è il punto in cui i valori stimati diventano propri. Non è solo che preferirei non eseguire effettivamente query potenzialmente costose sui database dei clienti. Interrogare la cache dei piani è una cosa, ma eseguire query per ottenere i dati effettivi è molto più difficile.
Con SQL 2016 SP1 installato, grazie a quell'elemento Connect, ora posso vedere la proprietà Numero stimato di righe da leggere nei piani stimati e nella cache dei piani. Il suggerimento dell'operatore mostrato qui è preso dalla cache e posso facilmente vedere che la proprietà Estimated mostra 7.276, così come l'avviso residuo:
Questo è qualcosa che potrei fare su una scatola del cliente, cercando nella cache situazioni in piani problematici in cui il rapporto tra il numero stimato di righe da leggere e il numero stimato di righe non è eccezionale. Potenzialmente, qualcuno potrebbe creare un processo che controlla tutti i piani nella cache, ma non è qualcosa che ho fatto io.
Una lettura attenta avrà notato che le righe effettive che sono uscite da questo operatore erano 149, che erano molto più piccole delle 1382,56 stimate. Ma quando cerco predicati residui che devono controllare troppe righe, il rapporto di 1.382,56 :7.276 è ancora significativo.
Ora che abbiamo scoperto che questa query è inefficace senza nemmeno bisogno di eseguirla, il modo per risolverlo è assicurarsi che il predicato residuo sia sufficientemente SARGable. Questa domanda...
SELECT COUNT(*) FROM Sales.Orders WHERE SalespersonPersonID = 7 AND OrderDate >= '20130401' AND OrderDate < '20130501';
…dà gli stessi risultati e non ha un predicato residuo. In questa situazione, il valore del Numero stimato di righe da leggere è identico al Numero stimato di righe e l'inefficienza è scomparsa:
Come accennato in precedenza, questo post fa parte del T-SQL Tuesday di questo mese. Perché non andare lì per vedere quali altre richieste di funzionalità sono state concesse di recente?