Я пытался диагностировать замедления в приложении. Для этого я зарегистрировал расширенные события SQL Server .
- Для этого вопроса я смотрю на одну конкретную хранимую процедуру.
- Но есть основной набор из дюжины хранимых процедур, которые в равной степени могут быть использованы в качестве расследования яблоки к яблокам
- и всякий раз, когда я вручную запускаю одну из хранимых процедур, она всегда работает быстро
- и если пользователь попытается снова: он будет работать быстро.
Время выполнения хранимой процедуры сильно различается. Многие из выполнений этой хранимой процедуры возвращаются в <1s:
И для этого "быстрого" ведра это намного меньше, чем 1 с. Это на самом деле около 90 мс:
Но есть длинный хвост пользователей, которые должны ждать 2 секунды, 3 секунды, 4 секунды. Некоторым приходится ждать 12, 13, 14. Тогда есть действительно бедные души, которые должны ждать 22, 23, 24.
И через 30 секунд клиентское приложение сдается, прерывает запрос, и пользователю пришлось ждать 30 секунд .
Корреляция, чтобы найти причинно-следственную связь
Поэтому я попытался соотнести:
- продолжительность против логического чтения
- продолжительность против физического чтения
- продолжительность против времени процессора
И никто, кажется, не дает никакой корреляции; кажется, никто не является причиной
Продолжительность и логическое чтение : при небольшом или большом количестве логических операций длительность все еще сильно колеблется :
Продолжительность по сравнению с физическим чтением : даже если запрос не был обработан из кэша и потребовалось много физических чтений, это не влияет на продолжительность:
Продолжительность по сравнению с временем процессора . Независимо от того, занимал ли запрос 0 с процессорного времени или целых 2.5 с процессорного времени, длительности имеют одинаковую изменчивость:
Бонус : я заметил, что Duration v Physical Reads и Duration v CPU time выглядят очень похоже. Это доказано, если я попытаюсь сопоставить процессорное время с физическими чтениями:
Оказывается, что загрузка ЦП происходит из-за ввода-вывода. Кто знал!
Так что, если в процессе выполнения запроса нет ничего, что могло бы объяснить разницу во времени выполнения, означает ли это, что это что-то не связанное с процессором или жестким диском?
Если бы узким местом были процессор или жесткий диск; не будет ли это узким местом?
Если мы предположим, что узким местом было именно процессор; что процессор недостаточно загружен для этого сервера:
- тогда не потребуются ли для выполнения, использующие больше процессорного времени, больше времени?
- так как они должны завершить с другими, используя перегруженный процессор?
Аналогично для жестких дисков. Если мы предположим, что жесткий диск был узким местом; что жестким дискам не хватает произвольной пропускной способности для этого сервера:
- тогда не будет ли выполнение с использованием большего количества физических чтений занимать больше времени?
- так как они должны завершить с другими, используя перегруженный ввод-вывод жесткого диска?
Сама хранимая процедура не выполняет и не требует никаких записей.
- Обычно он возвращает 0 строк (90%).
- Иногда он вернет 1 строку (7%).
- Редко он вернет 2 строки (1,4%).
- И в худшем случае он возвратил более 2 строк (один раз возвращая 12 строк)
Так что не похоже, что он возвращает безумный объем данных.
Использование ЦП сервера
Загрузка процессора процессором составляет в среднем около 1,8%, с периодическим всплеском до 18%, поэтому не похоже, что загрузка процессора является проблемой:
Таким образом, процессор сервера не перегружен.
Но сервер является виртуальным ...
Что-то за пределами вселенной?
Единственное, что я могу себе представить, это то, что существует вне вселенной сервера.
- если это не логично читает
- и это не физическое чтение
- и это не использование процессора
- и это не загрузка процессора
И это не похоже на параметры хранимой процедуры (потому что выдает тот же запрос вручную, и это не занимает 27 секунд - это занимает ~ 0 секунд).
Что еще может учитывать сервер, иногда требующий 30 секунд, а не 0 секунд, для запуска той же скомпилированной хранимой процедуры.
- контрольно-пропускные пункты?
Это виртуальный сервер
- хост перегружен?
- другая виртуальная машина на том же хосте?
Проходя через расширенные события сервера; больше ничего не происходит, когда запрос внезапно занимает 20 секунд. Он работает нормально, а затем решает не работать нормально:
- 2 секунды
- 1 секунда
- 30 секунд
- 3 секунды
- 2 секунды
И нет других особенно напряженных предметов, которые я могу найти. Это не во время каждого 2-часового резервного копирования журнала транзакций.
Что еще это может быть?
Есть ли что-нибудь, что я могу сказать, кроме: "сервер" ?
Изменить : коррелировать по времени суток
Я понял, что я связал продолжительность со всем:
- логическое чтение
- физическое чтение
- использование процессора
Но единственное, с чем я не соотносил это, было время суток . Возможно, проблема заключается в резервном копировании журнала транзакций каждые 2 часа .
Или , возможно, замедление этого происходит в патронах во время контрольно - пропускных пунктов?
Нет:
Intel Xeon Gold Quad-core 6142.
Редактировать - люди предполагают план выполнения запроса
Люди предполагают, что планы выполнения запросов должны различаться между «быстрым» и «медленным». Они не.
И мы можем увидеть это сразу после осмотра.
Мы знаем, что большая продолжительность вопроса не из-за «плохого» плана выполнения:
- тот, который взял более логичные чтения
- тот, который потреблял больше процессора из-за большего количества соединений и поиска ключей
Поскольку, если увеличение чтения или увеличение ЦП было причиной увеличения длительности запроса, то мы уже видели это выше. Там нет корреляции.
Но давайте попробуем сопоставить длительность с метрикой продукта области чтения ЦП:
Корреляции становится еще меньше - это парадокс.
Изменить : Обновлены диаграммы разброса, чтобы обойти ошибку в точечных диаграммах Excel с большим количеством значений.
Следующие шаги
Следующие мои шаги будут заключаться в том, чтобы заставить кого-то на сервере генерировать события для заблокированных запросов - через 5 секунд:
EXEC sp_configure 'blocked process threshold', '5';
RECONFIGURE
Не будет объяснено, заблокированы ли запросы на 4 секунды. Но, возможно, все, что блокирует запрос на 5 секунд, также блокирует некоторые на 4 секунды.
Медленные планы
Вот медленный план двух хранимых процедур:
- `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'
Одна и та же хранимая процедура с теми же параметрами запускается вплотную:
| 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 |
Звоните 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
Звонок 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
Имеет смысл, чтобы планы были идентичными; он выполняет ту же хранимую процедуру с теми же параметрами.








