Questa settimana insegneremo IEPTO2 a Dublino (e se l'Irlanda non è nella tua lista di posti da vedere in questa vita, devi aggiungerlo... è fantastico qui) e oggi ho finito il modulo Analisi del piano di query. Una cosa che tratterò sono cose interessanti che puoi trovare nel piano di query, ad esempio:
- NoJoinPredicate (2005 e versioni successive)
- ColumnsWithNoStatistics (2005 e versioni successive)
- UnmatchedIndexes (2008 e versioni successive)
- PlanAffectingConvert (2012 e versioni successive)
Questi attributi sono utili da cercare quando stai guardando un singolo piano, o una serie di piani, mentre stai sintonizzando. Ma se vuoi essere un po' più proattivo, puoi iniziare a estrarre la cache dei piani e cercarli lì. Ovviamente, per farlo è necessario scrivere un po' di XQuery, poiché i piani sono XML (per i dettagli sullo schema showplan, controlla:http://schemas.microsoft.com/sqlserver/2004/07/showplan/). Non amo XML, anche se non per mancanza di tentativi, e quando uno dei partecipanti ha chiesto se potevi acquisire query con l'attributo NoJoinPredicate tramite eventi estesi, ho pensato:"Che idea fantastica, dovrò controllare ."
Abbastanza sicuro, c'è un evento per quello. C'è un evento per tutti e quattro quelli che ho elencato sopra:
- predicato_unito_mancante
- statistiche_colonna_mancante
- indici_filtrati non corrispondenti
- plan_affecting_convert
Bello. L'impostazione di questi in una sessione di eventi estesi è piuttosto semplice. In questo caso, consiglierei di utilizzare la destinazione event_file, poiché probabilmente avvierai la sessione dell'evento e la lascerai funzionare per un po' prima di tornare indietro e rivedere l'output. Ho incluso alcune azioni, con la speranza che questi eventi non vengano attivati quello spesso, quindi non stiamo aggiungendo troppo sovraccarico qui. Ho incluso sql_text anche se non è un'azione su cui dovresti davvero fare affidamento. Jonathan ne ha già discusso in precedenza, ma sql_text ti sta solo dando l'inputbuffer, quindi potresti non ottenere la storia completa per la query. Per questo motivo, ho incluso anche plan_handle. L'avvertenza è che, a seconda di quando cerchi il piano, potrebbe non essere più nella cache del piano.
-- Remove event session if it exists IF EXISTS (SELECT 1 FROM [sys].[server_event_sessions] WHERE [name] = 'InterestingPlanEvents') BEGIN DROP EVENT SESSION [InterestingPlanEvents] ON SERVER END GO -- Define event session CREATE EVENT SESSION [InterestingPlanEvents] ON SERVER ADD EVENT sqlserver.missing_column_statistics ( ACTION(sqlserver.database_id,sqlserver.plan_handle,sqlserver.sql_text) WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)) AND [sqlserver].[database_id]>(4)) ), ADD EVENT sqlserver.missing_join_predicate ( ACTION(sqlserver.database_id,sqlserver.plan_handle,sqlserver.sql_text) WHERE ([sqlserver].[is_system]=(0) AND [sqlserver].[database_id]>(4)) ), ADD EVENT sqlserver.plan_affecting_convert ( ACTION(sqlserver.database_id,sqlserver.plan_handle,sqlserver.sql_text) WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)) AND [sqlserver].[database_id]>(4)) ), ADD EVENT sqlserver.unmatched_filtered_indexes ( ACTION(sqlserver.plan_handle,sqlserver.sql_text) WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)) AND [sqlserver].[database_id]>(4)) ) ADD TARGET package0.event_file ( SET filename=N'C:\temp\InterestingPlanEvents' /* change location if appropriate */ ) WITH (MAX_MEMORY=4096 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS, MAX_DISPATCH_LATENCY=5 SECONDS,MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=NONE, TRACK_CAUSALITY=ON,STARTUP_STATE=OFF) GO -- Start the event session ALTER EVENT SESSION [InterestingPlanEvents] ON SERVER STATE=START; GO
Una volta che la sessione dell'evento è attiva e funzionante, possiamo generare questi eventi con il codice di esempio riportato di seguito. Si noti che questo codice presuppone una nuova installazione di AdventureWorks2014. Se non ne hai uno, potresti non visualizzare l'evento di Missing_column_statistics se ti viene richiesta la colonna [HireDate] in [HumanResources].[Employee].
-- These queries assume a FRESH restore of AdventureWorks2014 ALTER DATABASE [AdventureWorks2014] SET AUTO_CREATE_STATISTICS OFF; GO USE [AdventureWorks2014]; GO CREATE INDEX [NCI_SalesOrderHeader] ON [Sales].[SalesOrderHeader] ( [PurchaseOrderNumber], [CustomerID], [TotalDue], [DueDate] ) WHERE [SubTotal] > 10000.00; GO /* No join predicate NOTE: We clear procedure here because the event ONLY fires for the *initial* compilation */ DBCC FREEPROCCACHE; /* Not for production use */ SELECT [h].[SalesOrderID], [d].[SalesOrderDetailID], [h].[CustomerID] FROM [Sales].[SalesOrderDetail] [d], [Sales].[SalesOrderHeader] [h] WHERE [d].[ProductID] = 897; GO -- Columns with no statistics SELECT [BusinessEntityID], [NationalIDNumber], [JobTitle], [HireDate], [ModifiedDate] FROM [HumanResources].[Employee] WHERE [HireDate] >= '2013-01-01'; GO -- Unmatched Index DECLARE @Total MONEY = 10000.00; SELECT [PurchaseOrderNumber], [CustomerID], [TotalDue], [DueDate] FROM [Sales].[SalesOrderHeader] WHERE [SubTotal] > @Total; GO -- Plan Affecting Convert SELECT [BusinessEntityID], [NationalIDNumber], [JobTitle], [HireDate], [ModifiedDate] FROM [HumanResources].[Employee] WHERE [NationalIDNumber] = 345106466; GO ALTER EVENT SESSION [InterestingPlanEvents] ON SERVER STATE=STOP; GO DROP EVENT SESSION [InterestingPlanEvents] ON SERVER; GO
NOTA:DOPO aver terminato di estrarre i piani dalla cache, è possibile eseguire l'istruzione ALTER per abilitare l'opzione di creazione automatica delle statistiche. In questo modo a questo punto si cancellerà la cache del piano e dovrai ricominciare da capo con i test. (E aspetta anche che tu abbia finito di eliminare l'indice.)
ALTER DATABASE [AdventureWorks2014] SET AUTO_CREATE_STATISTICS ON; GO DROP INDEX [NCI_SalesOrderHeader] ON [Sales].[SalesOrderHeader]; GO
Poiché ho interrotto la sessione dell'evento, apro il file di output in SSMS per vedere cosa abbiamo catturato:
Output da eventi estesi
Per la nostra prima query con un predicato di join mancante, abbiamo un evento che viene visualizzato e posso vedere il testo per la query nel campo sql_text. Tuttavia, quello che voglio davvero è guardare anche il piano, quindi posso prendere plan_handle e controllare sys.dm_exec_query_plan:
SELECT query_plan FROM sys.dm_exec_query_plan (0x06000700E2200333405DD12C0000000001000000000000000000000000000000000000000000000000000000);
E aprendolo in SQL Sentry Plan Explorer:
Predicato di partecipazione mancante
Il piano ha un indicatore visivo del predicato di join mancante nel ciclo nidificato (la X rossa) e se ci passo sopra vedo l'avviso (ed è nell'XML del piano). Eccellente... ora posso parlare con i miei sviluppatori della riscrittura di questa query.
L'evento successivo è per una statistica di colonna mancante. Ho forzato completamente questa situazione disattivando AUTO_CREATE_STATISTICS per il database AdventureWorks2014. Non lo consiglio in alcun modo, forma o forma. Questa opzione è abilitata per impostazione predefinita e consiglio di lasciarla sempre abilitata. Disattivarlo è il modo più semplice per generare questo evento, tuttavia. Ho di nuovo la query nel campo sql_text, ma userò di nuovo plan_handle per estrarre il piano:
SELECT query_plan FROM sys.dm_exec_query_plan (0x060007004448323810921C360000000001000000000000000000000000000000000000000000000000000000);
Statistica mancante
E abbiamo di nuovo un segnale visivo (il triangolo giallo con il punto esclamativo) per indicare che c'è un problema con il piano, e di nuovo è nell'XML. Da qui, verificherei prima se AUTO_CREATE_STATISTICS è disabilitato e, in caso contrario, inizierei a eseguire la query in Management Studio per vedere se riesco a ricreare l'avviso (e forzare la creazione delle statistiche).
Ora, gli eventi rimanenti sono un po' più interessanti.
Noterai che abbiamo tre eventi unmatched_filtered_indexes. Devo ancora determinare il motivo, ma ci sto lavorando e posterò nei commenti se/quando lo avrò ordinato. Per ora è sufficiente che io abbia l'evento, e all'interno dell'evento possiamo anche vedere le informazioni sull'oggetto, quindi conosco l'indice in questione:
Indice NCI_SalesOrderHeader referenziato da evento indice mancante
E posso di nuovo prendere il plan_handle per trovare il piano di query:
Indice senza corrispondenza
Questa volta vedo l'avviso nell'operatore SELECT, quindi so che c'è qualcosa che devo indagare ulteriormente. In questo caso, hai opzioni per fare in modo che l'ottimizzatore utilizzi l'indice filtrato quando utilizzi i parametri e ti consiglio di leggere il post di Aaron per ulteriori informazioni sull'uso degli indici filtrati.
Infine, abbiamo nove eventi per plan_affecting_convert. Che diamine? Sto ancora cercando di capire questo, ma ho usato l'opzione Traccia causalità per la mia sessione di eventi (durante il test) per confermare che tutti gli eventi fanno parte della stessa attività (lo sono). Se guardi l'elemento dell'espressione nell'output, vedrai che cambia leggermente (come fa compile_time), e questo viene emerso quando guardi i dettagli dell'avviso in Plan Explorer di SQL Sentry (vedi la seconda schermata di seguito). All'interno dell'output dell'evento, l'elemento dell'espressione fa dicci quale colonna è coinvolta, che è un inizio ma non abbastanza informazioni, quindi ancora una volta dobbiamo andare a prendere il piano:
SELECT query_plan FROM sys.dm_exec_query_plan (0x0600070023747010E09E1C360000000001000000000000000000000000000000000000000000000000000000);
Pianifica che interessa la conversione
Dettaglio conversione dal piano
Vediamo di nuovo il nostro amico, il triangolo giallo, nell'operatore SELECT, e all'interno dell'XML possiamo trovare l'attributo PlanAffectingConvert. Questo attributo è stato aggiunto nello schema showplan di SQL Server 2012, quindi se stai eseguendo una versione precedente, non lo vedrai nel piano. La risoluzione di questo avviso potrebbe richiedere un po' più di lavoro:è necessario capire dove si verifica una mancata corrispondenza del tipo di dati e perché, quindi iniziare a modificare il codice o lo schema... entrambi possono incontrare resistenza. Jonathan ha un post che discute la conversione implicita in modo più dettagliato, che è un buon punto di partenza se non hai mai lavorato con problemi di conversione in precedenza.
Riepilogo
La libreria di eventi estesi continua a crescere e una cosa da considerare durante la risoluzione dei problemi in SQL Server è se puoi ottenere le informazioni che stai cercando in un altro modo. Forse perché è più semplice (di sicuro preferisco XE a XML!), o perché è più efficiente, o ti dà più dettagli. Sia che tu stia cercando proattivamente problemi di query nel tuo ambiente o che tu stia reagendo a un problema segnalato da qualcuno ma hai difficoltà a trovarlo, gli eventi estesi sono un'opzione praticabile da considerare, in particolare poiché vengono aggiunte più nuove funzionalità a SQL Server.