Próbowałem zdiagnozować spowolnienia w aplikacji. W tym celu zarejestrowałem rozszerzone zdarzenia programu SQL Server .
- Na to pytanie patrzę na jedną konkretną procedurę przechowywaną.
- Ale istnieje podstawowy zestaw kilkunastu procedur przechowywanych, które równie dobrze mogą być wykorzystane jako dochodzenie od jabłka do jabłka
- i za każdym razem, gdy ręcznie uruchamiam jedną z procedur przechowywanych, zawsze działa szybko
- a jeśli użytkownik spróbuje ponownie: będzie działał szybko.
Czasy wykonania procedury przechowywanej różnią się bardzo. Wiele wykonań tej procedury składowanej zwraca się w ciągu <1s:
A dla tego „szybkiego” wiadra jest to znacznie mniej niż 1s. To właściwie około 90 ms:
Ale jest długi ogon użytkowników, którzy muszą czekać 2, 3, 4 sekundy. Niektórzy muszą czekać 12s, 13s, 14s. Są też naprawdę biedne dusze, które muszą czekać 22, 23, 24.
Po 30 latach aplikacja kliencka poddaje się, przerywa zapytanie, a użytkownik musiał czekać 30 sekund .
Korelacja w znalezieniu związku przyczynowego
Próbowałem więc skorelować:
- czas trwania a odczyty logiczne
- czas trwania a odczyty fizyczne
- czas trwania vs czas procesora
I wydaje się, że żaden nie daje żadnej korelacji; żaden nie wydaje się być przyczyną
czas trwania a odczyty logiczne : czy to małe, czy odczyty logiczne, czas trwania wciąż zmienia się gwałtownie :
czas trwania a fizyczne odczyty : nawet jeśli zapytanie nie zostało dostarczone z pamięci podręcznej i potrzebnych było wiele fizycznych odczytów, nie wpływa to na czas trwania:
czas trwania vs czas procesora : Niezależnie od tego, czy zapytanie zajęło 0 sekund procesora, czy pełne 2,5 sekundy procesora, czasy trwania mają tę samą zmienność:
Bonus : Zauważyłem, że czas trwania v Czytanie fizyczne i czas trwania v Czas procesora wyglądają bardzo podobnie. Jest to udowodnione, jeśli próbuję skorelować czas pracy procesora z odczytami fizycznymi:
Okazuje się, że dużo wykorzystania procesora pochodzi z I / O. Kto wiedział!
Więc jeśli nie ma nic w czynności wykonania zapytania, która może uwzględniać różnice w czasie wykonywania, czy to oznacza, że jest to coś niezwiązanego z procesorem lub dyskiem twardym?
Jeśli procesor lub dysk twardy były wąskim gardłem; czy nie byłoby to wąskie gardło?
Jeśli postawimy hipotezę, że wąskim gardłem był procesor; że procesor nie jest zasilany dla tego serwera:
- to czy wykonanie przy użyciu większej ilości czasu procesora nie potrwa dłużej?
- skoro muszą uzupełniać z innymi wykorzystującymi przeciążony procesor?
Podobnie w przypadku dysków twardych. Jeśli postawimy hipotezę, że dysk twardy był wąskim gardłem; że dyski twarde nie mają wystarczającej liczby losowych przepustowości dla tego serwera:
- to czy egzekucje wykorzystujące więcej fizycznych odczytów nie potrwałyby dłużej?
- skoro muszą uzupełniać z innymi używającymi przeciążonych dysków twardych we / wy?
Sama procedura przechowywana nie wykonuje ani nie wymaga zapisu.
- Zwykle zwraca 0 wierszy (90%).
- Czasami zwraca 1 wiersz (7%).
- Rzadko zwraca 2 wiersze (1,4%).
- W najgorszych przypadkach zwrócił więcej niż 2 wiersze (jeden raz zwraca 12 wierszy)
Więc to nie tak, że zwraca szaloną ilość danych.
Wykorzystanie procesora serwera
Średnie użycie procesora na serwerze wynosi około 1,8%, z okazjonalnym wzrostem nawet do 18% - więc nie wydaje się, aby obciążenie procesora stanowiło problem:
Więc procesor serwera nie wydaje się przeciążony.
Ale serwer jest wirtualny ...
Coś poza wszechświatem?
Jedyne, co mogę sobie wyobrazić, to coś, co istnieje poza wszechświatem serwera.
- jeśli to nie jest logiczne czyta
- i to nie są fizyczne odczyty
- i to nie jest użycie procesora
- i to nie jest obciążenie procesora
I to nie tak, że są to parametry procedury składowanej (ponieważ ręczne wykonanie tego samego zapytania i nie zajmuje 27 sekund - zajmuje ~ 0 sekund).
Co jeszcze może spowodować, że serwer potrzebuje 30 sekund, a nie 0 sekund, na uruchomienie tej samej skompilowanej procedury składowanej.
- punkty kontrolne?
To jest serwer wirtualny
- host przeciążony?
- kolejna maszyna wirtualna na tym samym hoście?
Przechodzenie przez rozszerzone zdarzenia serwera; nic innego nie dzieje się szczególnie, gdy zapytanie nagle zajmuje 20 sekund. Działa dobrze, a następnie decyduje się nie działać dobrze:
- 2 sekundy
- 1 sekunda
- 30 sekund
- 3 sekundy
- 2 sekundy
I nie mogę znaleźć innych szczególnie uciążliwych przedmiotów. Nie dzieje się to podczas każdej 2-godzinnej kopii zapasowej dziennika transakcji.
Co jeszcze może to być?
Czy jest coś, co mogę powiedzieć poza: „serwerem” ?
Edycja : Skoreluj według pory dnia
Zdałem sobie sprawę, że skorelowałem czasy trwania ze wszystkim:
- odczyty logiczne
- odczyty fizyczne
- użycie procesora
Ale jedyną rzeczą, z którą nie skorelowałem, była pora dnia . Być może problem stanowi kopia zapasowa dziennika transakcji co 2 godziny .
A może spowolnienie ma nastąpić w ciągu uchwytów kontrolnych?
Nie:
Czterordzeniowy Intel Xeon Gold 6142.
Edycja - ludzie hipotetycznie plan wykonania zapytania
Ludzie przypuszczają, że plany wykonania zapytań muszą różnić się między „szybkim” i „wolnym”. Oni nie są.
Widzimy to natychmiast po inspekcji.
Wiemy, że dłuższy czas trwania pytania nie wynika z „złego” planu wykonania:
- taki, który wymagał bardziej logicznych odczytów
- taki, który zużywał więcej procesora z większej liczby połączeń i wyszukiwań kluczy
Ponieważ jeśli wzrost odczytów lub wzrost procesora byłby przyczyną wydłużenia czasu trwania zapytania, to widzielibyśmy już to powyżej. Brak korelacji.
Spróbujmy jednak skorelować czas trwania z metryką produktu w obszarze odczytu procesora:
Korelacja staje się jeszcze mniejsza - co jest paradoksem.
Edycja : Zaktualizowano diagramy rozproszenia, aby obejść błąd w wykresach rozproszenia programu Excel z dużą liczbą wartości.
Następne kroki
Moim następnym krokiem będzie nakłonienie kogoś do wygenerowania przez serwer zdarzeń dla zablokowanych zapytań - po 5 sekundach:
EXEC sp_configure 'blocked process threshold', '5';
RECONFIGURE
Nie wyjaśnia, czy zapytania są blokowane przez 4 sekundy. Ale być może wszystko, co blokuje zapytanie na 5 sekund, blokuje również niektóre na 4 sekundy.
Slowplany
Oto powolny plan dwóch wykonywanych procedur przechowywanych:
- `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'
Ta sama procedura przechowywana, z tymi samymi parametrami, biegnie z powrotem do tyłu:
| 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 |
Zadzwoń 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
Zadzwoń 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
Sensowne jest, aby plany były identyczne; wykonuje tę samą procedurę przechowywaną z tymi samymi parametrami.