Su uno dei nostri clienti, abbiamo riscontrato alcuni problemi di prestazioni nella nostra applicazione. È un'app Web .NET 3.5 che utilizza e aggiorna i dati su un database SQL Server. Attualmente il nostro ambiente di produzione è costituito da un computer Windows 2008 R2 come front-end e da un cluster SQL Server 2008 R2 sul back-end. La nostra app utilizza COM + e MSDTC per connettersi al database.
Ecco cosa sta succedendo: i nostri utenti finali a volte lamentano lentezza nell'applicazione. Il caricamento di alcune pagine richiede più tempo del previsto. Durante il tentativo di capire cosa sta succedendo, sono riuscito a scoprire alcuni strani comportamenti sul lato del database che potrebbero essere la causa del degrado delle prestazioni. Ho notato che a volte ci sono alcune istruzioni SQL che richiedono molto più tempo per eseguire ciò che ci si aspetterebbe. Sono riuscito a identificare alcune di queste affermazioni (principalmente sono invocazioni di alcune delle procedure memorizzate della nostra applicazione) utilizzando una traccia del profiler (con il modello TSQL_Duration) per identificare le query di lunga durata.
Il problema è che quando eseguo queste procedure memorizzate direttamente sul database su SQL Management Studio a volte impiegano molto tempo (circa 7/8 secondi), altre volte sono veloci (meno di 1 secondo). Non so perché questo accada e mi sta facendo impazzire, perché la macchina SQL (4 core, 32 GB) non viene utilizzata da altre applicazioni e queste query non dovrebbero richiedere così tanto tempo per essere eseguite.
Non essendo un DBA o un guru di SQL Server, ho cercato di esaminare alcune cose che potrebbero aiutarmi a capire il problema. Ecco i passaggi che ho preso per cercare di risolvere il problema e quello che ho scoperto finora:
- Tutto il codice TSQL chiamato dall'applicazione è scritto in stored procedure.
- Ho identificato alcune delle query a esecuzione prolungata sul profiler di SQL Server, tuttavia quando le eseguo su Management Studio impiegano molto tempo per l'esecuzione (da 4 a 10 secondi) o l'esecuzione rapida (meno di 1 secondo). Sto eseguendo le stesse identiche query con gli stessi dati passati nei parametri. Queste query sono principalmente stored procedure con istruzioni selezionate in esse.
- Ho provato a guardare le statistiche di attese e code per cercare di capire se ci sono processi in attesa su alcune risorse. Ho eseguito la seguente query:
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
Ecco cosa ho scoperto:
- Dopo aver ripristinato le statistiche utilizzando DBCC SQLPERF (circa 1 o 2 ore dopo), i tipi di attesa che ho di più sono SOS_SCHEDULER_YIELD e WRITELOG
- Nel tempo (dopo circa 1 giorno di esecuzione), i tipi di attesa che si verificano maggiormente nel database sono CXPACKET (67%) e OLEDB (17%), anche se il tempo medio di attesa per ciascuno non è lungo. Ho anche notato che le istruzioni più lunghe identificate su SQL Profiler sono chiamate a procedure memorizzate che restituiscono più di un gruppo di risultati (spesso 3). Può esserci un problema di paralellismo qui? Esiste un modo per provare a identificare se questa è la causa del problema?
- Ho letto da qualche parte che le attese di OLEDB possono essere causate da chiamate a risorse OLEDB come server collegati. Abbiamo un server collegato per connetterci con una macchina dei servizi di indicizzazione (MSIDXS), tuttavia nessuna delle dichiarazioni identificate come di lunga durata fa uso di quel server collegato.
- Il tempo di attesa medio più alto che ho è per le attese di tipo LCK_M_X (circa 1,5 secondi in media), ma questi tipi di attesa non si verificano molto spesso rispetto ad altri tipi (ad esempio, 64 LCK_M_X attende rispetto a 10.823 CXPACKET attende nello stesso periodo di tempo ).
- Una cosa che ho notato è che il servizio MSDTC non è cluster. Il servizio SQL Server è in cluster ma non MSDTC. Può esserci un successo nelle prestazioni a causa di questo? Stiamo utilizzando MSDTC perché la nostra app utilizza Enterprise Services (DCOM) per accedere al database, ma i server non sono stati installati e configurati da noi, ma dal nostro client.
Qualcuno può aiutarmi a dare un po 'più di senso a questi dati? Qualcuno può darmi una mano per capire cosa può succedere? C'è qualcosa che posso fare sul server per provare a capire le cose? Devo parlare con il team di sviluppo delle applicazioni?
exec()funzione spiegherebbe il comportamento osservato. In questo caso l'usosp_executesqlnormale risolve i problemi con istruzioni SQL dinamiche.