Я намагався діагностувати уповільнення у програмі. Для цього я зареєстрував розширені події SQL Server .
- Для цього питання я розглядаю одну конкретну збережену процедуру.
- Але є основний набір з десятка збережених процедур, які однаково можуть бути використані як розслідування від яблук до яблук
- і коли я вручну запускаю одну із збережених процедур, вона завжди працює швидко
- і якщо користувач спробує ще раз: він запуститься швидко.
Часи виконання збереженої процедури значно відрізняються. Багато виконань цієї збереженої процедури повертається в <1s:
А для цього «швидкого» відра - це набагато менше 1 с. Це насправді близько 90 мс:
Але є довгий хвіст користувачів, яким доводиться чекати 2, 3, 4 секунди. Деяким доводиться чекати 12s, 13s, 14s. Тоді є справді бідні душі, яким доводиться чекати 22, 23, 24 години.
А після 30-х років клієнтська програма здається, перериває запит, і користувачеві довелося чекати 30 секунд .
Кореляція для пошуку причинного зв'язку
Тому я спробував співвіднести:
- тривалість та логічне зчитування
- тривалість та фізичні показання
- тривалість vs час процесора
І нібито жодна кореляція не дає; ніхто, здається, не є причиною
тривалість порівняно з логічним читанням : чи мало, чи багато логічного читання, тривалість все ще сильно коливається :
тривалість порівняно з фізичними читаннями : навіть якщо запит не надходив з кеша, і було потрібно багато фізичних читань, це не впливає на тривалість:
тривалість порівняно з процесорним часом : Незалежно від того, чи запит зайняв 0 с CPU, або 2,5 секунди часу процесора, тривалість має однакову мінливість:
Бонус : я помітив, що тривалість v «Фізичні читання» та « Тривалість проти часу процесора» виглядають дуже схоже. Це доведено, якщо я спробую співвіднести час процесора з фізичним читанням:
Виявляється, багато використання процесора відбувається від вводу / виводу. Хто знав!
Отже, якщо в акті виконання запиту немає нічого, що може враховувати відмінності у часі виконання, чи означає це, що це щось не пов'язане з процесором або жорстким диском?
Якщо процесор або жорсткий диск були вузьким місцем; хіба це не було вузьким місцем?
Якщо ми припускаємо гіпотезу, що саме вузол був саме процесором; що ЦП не працює на цьому сервері:
- то чи не будуть тривати виконання часу, що використовують більше процесорного часу?
- оскільки їм доводиться комплектуватися з іншими, використовуючи перевантажений процесор?
Аналогічно для жорстких дисків. Якщо припустити, що жорсткий диск був вузьким місцем; що на жорсткому диску не вистачає випадкових наборів для цього сервера:
- то чи не будуть тривати страти за допомогою більш фізичного читання довше?
- оскільки вони мають комплектуватися з іншими, використовуючи перевантажений жорсткий диск вводу / виводу?
Сама збережена процедура ні записує, ні вимагає.
- Зазвичай він повертає 0 рядків (90%).
- Іноді він поверне 1 ряд (7%).
- Рідко він поверне 2 ряди (1,4%).
- І в гірших випадках він повернув понад 2 ряди (один раз повертаючи 12 рядків)
Тож це не так, як це повернення шаленого обсягу даних.
Використання серверного процесора
Використання процесора на сервері в середньому становить близько 1,8%, з випадковим стрибком до 18% - тому, здається, не так це завантаження процесора:
Тому процесор сервера не здається перевантаженим.
Але сервер є віртуальним ...
Щось поза Всесвітом?
Єдине, що я можу собі уявити, - це те, що існує поза всесвіту сервера.
- якщо це не логічно читає
- і це не фізичні читання
- і це не використання процесора
- і це не завантаження процесора
І це не так, як це параметри збереженої процедури (адже видача одного і того ж запиту вручну і не займає 27 секунд - це займає ~ 0 секунд).
Що ще може враховувати, що сервер іноді займає 30 секунд, а не 0 секунд, щоб запустити ту ж компільовану збережену процедуру.
- пункти пропуску?
Це віртуальний сервер
- господар перевантажений?
- інший VM на тому ж хості?
Проходження через розширені події сервера; нічого іншого особливо не відбувається, коли запит раптово займає 20 секунд. Він працює добре, потім вирішує не працювати добре:
- 2 секунди
- 1 секунда
- 30 секунд
- 3 секунди
- 2 секунди
І немає інших особливо напружених предметів, які я можу знайти. Це не під час резервного копіювання журналу кожні 2 години.
Що ще могло бути?
Чи я можу сказати окрім: "сервер" ?
Редагувати : співвідносити час доби
Я зрозумів, що я співвідносив тривалість з усім:
- логічні читання
- фізичні читання
- використання процесора
Але одне, з чим я не співвідносився, це час доби . Можливо, резервна копія журналу транзакцій кожні 2 години є проблемою.
Або , можливо, уповільнення цього відбувається в патронах під час контрольно - пропускних пунктів?
Ні:
Чотириядерний Intel Xeon Gold 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
Є сенс, щоб плани були однаковими; він виконує ту саму збережену процедуру, з тими ж параметрами.