Ho provato a diagnosticare rallentamenti in un'applicazione. Per questo ho registrato gli eventi estesi di SQL Server .
- Per questa domanda sto guardando una particolare procedura memorizzata.
- Ma ci sono una serie base di una dozzina di procedure memorizzate che possono ugualmente essere usate come un'indagine da mela a mela
- e ogni volta che eseguo manualmente una delle procedure memorizzate, funziona sempre velocemente
- e se un utente riprova: funzionerà velocemente.
I tempi di esecuzione della procedura memorizzata variano notevolmente. Molte esecuzioni di questa procedura memorizzata ritornano in <1s:
E per quel secchio "veloce" , è molto meno di 1 secondo. In realtà sono circa 90 ms:
Ma c'è una lunga coda di utenti che devono aspettare 2 secondi, 3 secondi, 4 secondi. Alcuni devono aspettare 12, 13, 14 secondi. Poi ci sono anime davvero povere che devono aspettare 22, 23, 24.
E dopo 30 anni, l'applicazione client si arrende, interrompe la query e l'utente ha dovuto attendere 30 secondi .
Correlazione per trovare la causalità
Quindi ho cercato di correlare:
- durata vs letture logiche
- durata vs letture fisiche
- durata vs tempo cpu
E nessuno sembra dare alcuna correlazione; nessuno sembra essere la causa
durata vs letture logiche : che siano un po 'o molte letture logiche, la durata oscilla ancora selvaggiamente :
durata vs letture fisiche : anche se la query non è stata pubblicata dalla cache e sono necessarie molte letture fisiche, non influisce sulla durata:
durata vs tempo CPU : se la query ha impiegato 0 secondi di tempo CPU o 2,5 secondi di tempo CPU, le durate hanno la stessa variabilità:
Bonus : ho notato che Durata v Letture fisiche e Durata v Tempo CPU sembrano molto simili. Questo è dimostrato se provo a correlare il tempo della CPU con le letture fisiche:
Risulta che un sacco di utilizzo della CPU proviene dall'I / O. Chi lo sapeva!
Quindi, se non c'è nulla sull'atto di eseguire la query in grado di tenere conto delle differenze nei tempi di esecuzione, ciò implica che è qualcosa di non correlato alla CPU o al disco rigido?
Se la CPU o il disco rigido fossero il collo di bottiglia; non sarebbe il collo di bottiglia?
Se ipotizziamo che fosse la CPU a costituire il collo di bottiglia; che la CPU non è alimentata per questo server:
- quindi le esecuzioni che impiegano più tempo della CPU richiederebbero più tempo?
- dal momento che devono completare con gli altri utilizzando la CPU sovraccarica?
Allo stesso modo per i dischi rigidi. Se ipotizziamo che il disco rigido fosse un collo di bottiglia; che i dischi rigidi non abbiano un through-put casuale sufficiente per questo server:
- allora le esecuzioni che usano più letture fisiche non richiederebbero più tempo?
- dal momento che devono completare con altri utilizzando l'I / O del disco rigido sovraccaricato?
La procedura memorizzata non esegue né scrive né richiede alcuna scrittura.
- Di solito restituisce 0 righe (90%).
- Occasionalmente restituisce 1 riga (7%).
- Raramente restituirà 2 righe (1,4%).
- E nei casi peggiori ha restituito più di 2 righe (una volta restituendo 12 righe)
Quindi non è come restituire un volume folle di dati.
Utilizzo della CPU del server
L' utilizzo del processore del server è in media dell'1,8% circa, con un picco occasionale fino al 18%, quindi non sembra che il carico della CPU sia un problema:
Quindi la CPU del server non sembra sovraccarica.
Ma il server è virtuale ...
Qualcosa al di fuori dell'universo?
L'unica cosa che posso immaginare è qualcosa che esiste al di fuori dell'universo del server.
- se non sono letture logiche
- e non sono letture fisiche
- e non è l'utilizzo della CPU
- e non è carico della CPU
E non è come se fossero i parametri della procedura memorizzata (perché emette la stessa query manualmente e non richiede 27 secondi - ci vogliono ~ 0 secondi).
Cos'altro potrebbe rendere conto che il server impiega a volte 30 secondi, anziché 0 secondi, per eseguire la stessa procedura memorizzata compilata.
- posti di blocco?
È un server virtuale
- l'host è sovraccarico?
- un'altra macchina virtuale sullo stesso host?
Passando attraverso gli eventi estesi del server; non succede nient'altro in particolare quando una query richiede improvvisamente 20 secondi. Funziona bene, quindi decide di non funzionare bene:
- 2 secondi
- 1 secondo
- 30 secondi
- 3 secondi
- 2 secondi
E non ci sono altri oggetti particolarmente faticosi che posso trovare. Non è durante il backup del registro delle transazioni ogni 2 ore.
Cos'altro potrebbe essere?
C'è qualcosa che posso dire oltre: "il server" ?
Modifica : correlato per ora del giorno
Mi sono reso conto di aver correlato le durate a tutto:
- letture logiche
- letture fisiche
- uso della CPU
Ma l'unica cosa a cui non l'ho correlato era l' ora del giorno . Forse il backup del registro delle transazioni ogni 2 ore è un problema.
O forse i rallentamenti si verificano nei mandrini durante i checkpoint?
No:
Intel Xeon Gold Quad-core 6142.
Modifica: le persone stanno ipotizzando il piano di esecuzione della query
Le persone stanno ipotizzando che i piani di esecuzione delle query debbano essere diversi tra "veloce" e "lento". Non sono.
E possiamo vederlo immediatamente dall'ispezione.
Sappiamo che la durata della domanda più lunga non è dovuta a un piano di esecuzione "scadente":
- uno che ha preso più letture logiche
- uno che ha consumato più CPU da più join e ricerche chiave
Perché se un aumento delle letture o un aumento della CPU fosse una causa della maggiore durata della query, lo avremmo già visto sopra. Non c'è correlazione.
Ma proviamo a correlare la durata con la metrica del prodotto dell'area di lettura CPU:
La correlazione diventa ancora meno, il che è un paradosso.
Modifica : aggiornati i diagrammi a dispersione per aggirare un bug nei grafici a dispersione di Excel con un numero elevato di valori.
Prossimi passi
I miei prossimi passi saranno far sì che qualcuno debba server generare eventi per query bloccate - dopo 5 secondi:
EXEC sp_configure 'blocked process threshold', '5';
RECONFIGURE
Non spiegherà se le query vengono bloccate per 4 secondi. Ma forse tutto ciò che blocca una query per 5 secondi ne blocca anche una per 4 secondi.
I piani lenti
Ecco il piano lento delle due stored procedure in esecuzione:
- `EXECUTE FindFrob @CustomerID = 7383, @StartDate = '20190725 04: 00: 00.000', @EndDate = '20190726 04: 00: 00.000'
- `EXECUTE FindFrob @CustomerID = 7383, @StartDate = '20190725 04: 00: 00.000', @EndDate = '20190726 04: 00: 00.000'
La stessa procedura memorizzata, con gli stessi parametri, esegue back to back:
| Duration (us) | CPU time (us) | Logical reads | Physical reads |
|---------------|---------------|---------------|----------------|
| 13,984,446 | 47,000 | 5,110 | 771 |
| 4,603,566 | 47,000 | 5,126 | 740 |
Chiamata 1:
|--Nested Loops(Left Semi Join, OUTER REFERENCES:([Contoso2].[dbo].[Frobs].[FrobGUID]) OPTIMIZED)
|--Nested Loops(Inner Join, OUTER REFERENCES:([Contoso2].[dbo].[FrobTransactions].[OnFrobGUID]))
| |--Nested Loops(Inner Join, OUTER REFERENCES:([Contoso2].[dbo].[FrobTransactions].[RowNumber]) OPTIMIZED)
| | |--Nested Loops(Inner Join, OUTER REFERENCES:([tpi].[TransactionGUID]) OPTIMIZED)
| | | |--Nested Loops(Inner Join, OUTER REFERENCES:([tpi].[TransactionGUID]) OPTIMIZED)
| | | | |--Index Seek(OBJECT:([Contoso2].[dbo].[TransactionPatronInfo].[IX_TransactionPatronInfo_CustomerID_TransactionGUID] AS [tpi]), SEEK:([tpi].[CustomerID]=[@CustomerID]) ORDERED FORWARD)
| | | | |--Index Seek(OBJECT:([Contoso2].[dbo].[Transactions].[IX_Transactions_TransactionGUIDTransactionDate]), SEEK:([Contoso2].[dbo].[Transactions].[TransactionGUID]=[Contoso2].[dbo
| | | |--Index Seek(OBJECT:([Contoso2].[dbo].[FrobTransactions].[IX_FrobTransactions2_MoneyAppearsOncePerTransaction]), SEEK:([Contoso2].[dbo].[FrobTransactions].[TransactionGUID]=[Contos
| | |--Clustered Index Seek(OBJECT:([Contoso2].[dbo].[FrobTransactions].[IX_FrobTransactions_RowNumber]), SEEK:([Contoso2].[dbo].[FrobTransactions].[RowNumber]=[Contoso2].[dbo].[Fin
| |--Clustered Index Seek(OBJECT:([Contoso2].[dbo].[Frobs].[PK_Frobs_FrobGUID]), SEEK:([Contoso2].[dbo].[Frobs].[FrobGUID]=[Contoso2].[dbo].[FrobTransactions].[OnFrobGUID]), WHERE:([Contos
|--Filter(WHERE:([Expr1009]>(1)))
|--Compute Scalar(DEFINE:([Expr1009]=CONVERT_IMPLICIT(int,[Expr1012],0)))
|--Stream Aggregate(DEFINE:([Expr1012]=Count(*)))
|--Index Seek(OBJECT:([Contoso2].[dbo].[FrobTransactions].[IX_FrobTransactins_OnFrobGUID]), SEEK:([Contoso2].[dbo].[FrobTransactions].[OnFrobGUID]=[Contoso2].[dbo].[Frobs].[LC
Chiama 2
|--Nested Loops(Left Semi Join, OUTER REFERENCES:([Contoso2].[dbo].[Frobs].[FrobGUID]) OPTIMIZED)
|--Nested Loops(Inner Join, OUTER REFERENCES:([Contoso2].[dbo].[FrobTransactions].[OnFrobGUID]))
| |--Nested Loops(Inner Join, OUTER REFERENCES:([Contoso2].[dbo].[FrobTransactions].[RowNumber]) OPTIMIZED)
| | |--Nested Loops(Inner Join, OUTER REFERENCES:([tpi].[TransactionGUID]) OPTIMIZED)
| | | |--Nested Loops(Inner Join, OUTER REFERENCES:([tpi].[TransactionGUID]) OPTIMIZED)
| | | | |--Index Seek(OBJECT:([Contoso2].[dbo].[TransactionPatronInfo].[IX_TransactionPatronInfo_CustomerID_TransactionGUID] AS [tpi]), SEEK:([tpi].[CustomerID]=[@CustomerID]) ORDERED FORWARD)
| | | | |--Index Seek(OBJECT:([Contoso2].[dbo].[Transactions].[IX_Transactions_TransactionGUIDTransactionDate]), SEEK:([Contoso2].[dbo].[Transactions].[TransactionGUID]=[Contoso2].[dbo
| | | |--Index Seek(OBJECT:([Contoso2].[dbo].[FrobTransactions].[IX_FrobTransactions2_MoneyAppearsOncePerTransaction]), SEEK:([Contoso2].[dbo].[FrobTransactions].[TransactionGUID]=[Contos
| | |--Clustered Index Seek(OBJECT:([Contoso2].[dbo].[FrobTransactions].[IX_FrobTransactions_RowNumber]), SEEK:([Contoso2].[dbo].[FrobTransactions].[RowNumber]=[Contoso2].[dbo].[Fin
| |--Clustered Index Seek(OBJECT:([Contoso2].[dbo].[Frobs].[PK_Frobs_FrobGUID]), SEEK:([Contoso2].[dbo].[Frobs].[FrobGUID]=[Contoso2].[dbo].[FrobTransactions].[OnFrobGUID]), WHERE:([Contos
|--Filter(WHERE:([Expr1009]>(1)))
|--Compute Scalar(DEFINE:([Expr1009]=CONVERT_IMPLICIT(int,[Expr1012],0)))
|--Stream Aggregate(DEFINE:([Expr1012]=Count(*)))
|--Index Seek(OBJECT:([Contoso2].[dbo].[FrobTransactions].[IX_FrobTransactins_OnFrobGUID]), SEEK:([Contoso2].[dbo].[FrobTransactions].[OnFrobGUID]=[Contoso2].[dbo].[Frobs].[LC
Ha senso che i piani siano identici; sta eseguendo la stessa procedura memorizzata, con gli stessi parametri.