У одного з наших клієнтів у нас виникли деякі проблеми з ефективністю нашого додатку. Це веб-додаток .NET 3.5, який споживає та оновлює дані в базі даних SQL Server. В даний час наше виробниче середовище складається з машини Windows 2008 R2 як передньої частини та кластера SQL Server 2008 R2 на задньому. Наш додаток використовує COM + та MSDTC для підключення до бази даних.
Ось що відбувається: наші кінцеві користувачі іноді скаржаться на повільність у програмі. Для завантаження деяких сторінок потрібно трохи більше часу, ніж очікувалося. Намагаючись з’ясувати, що відбувається, мені вдалося з’ясувати якусь дивну поведінку на стороні бази даних, яка може бути причиною погіршення продуктивності. Я помітив, що іноді є деякі заяви SQL, які потребують набагато більше часу для виконання того, що можна було б очікувати. Мені вдалося ідентифікувати деякі з цих висловлювань (в основному це виклики деяких збережених процедур нашого додатку), використовуючи трафік профілю (з шаблоном TSQL_Duration) для ідентифікації тривалих запитів.
Проблема полягає в тому, що коли я запускаю ці збережені процедури безпосередньо в базі даних на SQL Management Studio, іноді вони займають багато часу (приблизно 7/8 секунд), в іншому випадку вони швидкі (менше 1 секунди). Я не знаю, чому це відбувається, і це зводить мене з глузду, тому що SQL-машина (4 ядра, 32 ГБ) не використовується жодними іншими програмами, і ці запити не повинні зайняти це багато часу.
Не будучи DBA або гуру SQL Server, я намагався розглянути деякі речі, які можуть допомогти мені зрозуміти проблему. Ось кроки, які я вжив, щоб спробувати розібратися в проблемі і що я дізнався до цих пір:
- Весь код TSQL, викликаний програмою, записується в збережені процедури.
- Я визначив деякі тривалі запити у профіле SQL Server, однак, коли я запускаю їх у програмі Management Studio, вони запускають багато часу (від 4 до 10 сек.), Або швидко виконують (менше 1 сек.). Я запускаю абсолютно ті самі запити з тими ж даними, що передаються в параметрах. Ці запити в основному зберігаються в процедурах із виділеними операторами.
- Я спробував переглянути статистику очікувань та черг, щоб спробувати визначити, чи є процеси, які чекають на деяких ресурсах. Я запустив наступний запит:
WITH Waits AS
(SELECT
wait_type,
wait_time_ms / 1000.0 AS WaitS,
(wait_time_ms - signal_wait_time_ms) / 1000.0 AS ResourceS,
signal_wait_time_ms / 1000.0 AS SignalS,
waiting_tasks_count AS WaitCount,
100.0 * wait_time_ms / SUM (wait_time_ms) OVER() AS Percentage,
ROW_NUMBER() OVER(ORDER BY wait_time_ms DESC) AS RowNum
FROM sys.dm_os_wait_stats
WHERE wait_type NOT IN (
'CLR_SEMAPHORE', 'LAZYWRITER_SLEEP', 'RESOURCE_QUEUE', 'SLEEP_TASK',
'SLEEP_SYSTEMTASK', 'SQLTRACE_BUFFER_FLUSH', 'WAITFOR', 'LOGMGR_QUEUE',
'CHECKPOINT_QUEUE', 'REQUEST_FOR_DEADLOCK_SEARCH', 'XE_TIMER_EVENT', 'BROKER_TO_FLUSH',
'BROKER_TASK_STOP', 'CLR_MANUAL_EVENT', 'CLR_AUTO_EVENT', 'DISPATCHER_QUEUE_SEMAPHORE',
'FT_IFTS_SCHEDULER_IDLE_WAIT', 'XE_DISPATCHER_WAIT', 'XE_DISPATCHER_JOIN', 'BROKER_EVENTHANDLER',
'TRACEWRITE', 'FT_IFTSHC_MUTEX', 'SQLTRACE_INCREMENTAL_FLUSH_SLEEP',
'BROKER_RECEIVE_WAITFOR', 'ONDEMAND_TASK_QUEUE', 'DBMIRROR_EVENTS_QUEUE',
'DBMIRRORING_CMD', 'BROKER_TRANSMITTER', 'SQLTRACE_WAIT_ENTRIES',
'SLEEP_BPOOL_FLUSH', 'SQLTRACE_LOCK')
)
SELECT
W1.wait_type AS WaitType,
CAST (W1.WaitS AS DECIMAL(14, 2)) AS Wait_S,
CAST (W1.ResourceS AS DECIMAL(14, 2)) AS Resource_S,
CAST (W1.SignalS AS DECIMAL(14, 2)) AS Signal_S,
W1.WaitCount AS WaitCount,
CAST (W1.Percentage AS DECIMAL(4, 2)) AS Percentage,
CAST ((W1.WaitS / W1.WaitCount) AS DECIMAL (14, 4)) AS AvgWait_S,
CAST ((W1.ResourceS / W1.WaitCount) AS DECIMAL (14, 4)) AS AvgRes_S,
CAST ((W1.SignalS / W1.WaitCount) AS DECIMAL (14, 4)) AS AvgSig_S
FROM Waits AS W1
INNER JOIN Waits AS W2 ON W2.RowNum <= W1.RowNum
GROUP BY W1.RowNum, W1.wait_type, W1.WaitS, W1.ResourceS, W1.SignalS, W1.WaitCount, W1.Percentage
HAVING SUM (W2.Percentage) - W1.Percentage < 95; -- percentage threshold
GO
Ось що я дізнався:
- Після того як я скинув статистику за допомогою DBCC SQLPERF (приблизно через 1 або 2 години після), типи очікування, які у мене найбільше, - це SOS_SCHEDULER_YIELD і WRITELOG
- З часом (після приблизно 1-денного виконання) типи очікування, які найбільше трапляються в базі даних, - це CXPACKET (67%) і OLEDB (17%), хоча середній час очікування для кожного не є довгим. Я також зауважив, що довші запущені оператори, визначені в SQL Profiler, - це виклики до збережених процедур, які повертають більше одного набору результатів (часто 3). Чи може тут виникнути проблема паралелізму? Чи я можу спробувати визначити, чи це причина проблеми?
- Я десь читав, що очікування OLEDB може бути викликано дзвінками до ресурсів OLEDB, як-от пов'язані сервери. У нас є зв'язаний сервер для з'єднання з машиною служб індексування (MSIDXS), однак жодна з заяв, ідентифікованих як давно запущена, не використовує цей пов'язаний сервер.
- Вищий середній час очікування, який я маю, - це очікування типу LCK_M_X (середнє приблизно 1,5 секунди), але такі типи очікування трапляються не дуже часто порівняно з іншими типами (наприклад, 64 LCK_M_X очікує проти 10 823 CXPACKET очікує за той самий проміжок часу ).
- Я помітив одне, що служба MSDTC не кластеризована. Служба SQL Server кластеризована, але не MSDTC. Чи може через це бути хіт вистави? Ми використовуємо MSDTC, оскільки наш додаток використовує Enterprise Services (DCOM) для доступу до бази даних, але сервери були встановлені не та не налаштовані нами, а нашим клієнтом.
Хтось може допомогти мені зрозуміти ці дані? Чи може хтось подати мені руку на розуміння того, що може статися? Чи можу я щось зробити на сервері, щоб спробувати розібратися? Чи варто поговорити з командою з розробки додатків?
exec()
функції пояснить спостережувану поведінку. У цьому випадку використання,sp_executesql
як правило, вирішує проблеми з динамічними операторами SQL.