У одного из наших клиентов были проблемы с производительностью нашего приложения. Это веб-приложение .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 ГБ) не используется никакими другими приложениями, и эти запросы не должны запускаться так долго.
Не будучи администратором базы данных или гуру 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.