Di recente ho riscontrato un elevato TRANSACTION_MUTEX
tempo di attesa accumulato su un sistema client. Non riuscivo a ricordare un caso in cui ho visto questo tipo di attesa in cima all'elenco delle "attese elevate" ed ero curioso di sapere quali fattori potrebbero spingere questo tipo di tempo di attesa complessivo.
La definizione della documentazione in linea di TRANSACTION_MUTEX
è che "si verifica durante la sincronizzazione dell'accesso a una transazione da più batch". Non molte aree all'interno del motore di SQL Server espongono questo tipo di funzionalità, quindi la mia indagine è stata ristretta alle seguenti tecnologie:
- Il deprecato
sp_getbindtoken
esp_bindsession
procedure memorizzate di sistema utilizzate per gestire le connessioni associate - Transazioni distribuite
- MARS (set di risultati attivi multipli)
Il mio obiettivo era testare ogni tecnologia e vedere se ha influenzato il TRANSACTION_MUTEX
tipo di attesa.
Il primo test che ho eseguito ha utilizzato il deprecato sp_getbindtoken
e sp_bindsession
procedura di archiviazione. Il sp_getbindtoken
restituisce un identificatore di transazione che può quindi essere utilizzato da sp_bindsession
per associare più sessioni sulla stessa transazione.
Prima di ogni scenario di test, mi sono assicurato di cancellare le statistiche di attesa dell'istanza di SQL Server di test:
DBCC SQLPERF('waitstats', CLEAR); GO
La mia istanza di prova di SQL Server eseguiva SQL Server 2012 SP1 Developer Edition (11.0.3000). Ho utilizzato il database di esempio Credit, sebbene tu possa utilizzare qualsiasi altro tipo di database di esempio come AdventureWorks se lo desideri, poiché lo schema e la distribuzione dei dati non sono direttamente pertinenti all'argomento di questo articolo e non erano necessari per guidare il TRANSACTION_MUTEX
tempo di attesa.
sp_getbindtoken / sp_bindsession
Nella prima finestra di sessione di SQL Server Management Studio, ho eseguito il codice seguente per avviare una transazione e generare il token di collegamento per l'arruolamento da parte delle altre sessioni pianificate:
USE Credit; GO BEGIN TRANSACTION; DECLARE @out_token varchar(255); EXECUTE sp_getbindtoken @out_token OUTPUT; SELECT @out_token AS out_token; GO
Questo ha restituito un @out_token
di S/Z5_GOHLaGY<^i]S9LXZ-5---.fE---
. In due finestre di query separate di SQL Server Management Studio, ho eseguito il codice seguente per unirmi alle sessioni esistenti (accedendo alla transazione condivisa):
USE Credit; GO EXEC sp_bindsession 'S/Z5_GOHLaGY<^i]S9LXZ-5---.fE---';
E con la prima finestra di sessione ancora aperta, ho avviato il ciclo seguente per aggiornare la tabella della tabella degli addebiti con una data di addebito uguale alla data e all'ora correnti, quindi ho eseguito la stessa logica nelle altre due finestre (tre sessioni attive nel ciclo):
WHILE 1 = 1 BEGIN UPDATE dbo.charge SET charge_dt = SYSDATETIME(); END
Dopo alcuni secondi, ho annullato ogni query in esecuzione. Delle tre sessioni, solo una è stata in grado di eseguire effettivamente gli aggiornamenti, anche se le altre due sessioni sono state attivamente unite alla stessa transazione. E se guardassi il TRANSACTION_MUTEX
tipo wait, posso vedere che è stato effettivamente incrementato:
SELECT [wait_type], [waiting_tasks_count], [wait_time_ms], [max_wait_time_ms], [signal_wait_time_ms] FROM sys.dm_os_wait_stats WHERE wait_type = 'TRANSACTION_MUTEX';
I risultati di questo particolare test sono stati i seguenti:
wait_type waiting_tasks_count wait_time_ms max_wait_time_ms signal_wait_time_ms TRANSACTION_MUTEX 2 181732 93899 0
Quindi vedo che c'erano due attività in attesa (le due sessioni che stavano tentando contemporaneamente di aggiornare la stessa tabella tramite il ciclo). Dal momento che non avevo eseguito SET NOCOUNT ON
, ho potuto vedere che solo il primo UPDATE
eseguito loop ha apportato modifiche. Ho provato questa tecnica simile usando alcune varianti diverse (ad esempio – quattro sessioni complessive, con tre in attesa) – e il TRANSACTION_MUTEX
l'incremento ha mostrato un comportamento simile. Ho visto anche il TRANSACTION_MUTEX
accumulazione durante l'aggiornamento simultaneo di una tabella diversa per ogni sessione, quindi non erano necessarie modifiche allo stesso oggetto in un ciclo per riprodurre il TRANSACTION_MUTEX
accumulo di tempo di attesa.
Transazioni distribuite
Il mio prossimo test consisteva nel vedere se TRANSACTION_MUTEX
il tempo di attesa è stato incrementato per le transazioni distribuite. Per questo test, ho utilizzato due istanze di SQL Server e un server collegato collegato tra loro due. MS DTC era in esecuzione e configurato correttamente e ho eseguito il codice seguente che ha eseguito un DELETE
locale e un DELETE
remoto tramite il server collegato e quindi ripristinato le modifiche:
USE Credit; GO SET XACT_ABORT ON; -- Assumes MS DTC service is available, running, properly configured BEGIN DISTRIBUTED TRANSACTION; DELETE [dbo].[charge] WHERE charge_no = 1; DELETE [JOSEPHSACK-PC\AUGUSTUS].[Credit].[dbo].[charge] WHERE charge_no = 1; ROLLBACK TRANSACTION;
Il TRANSACTION_MUTEX
non ha mostrato attività sul server locale:
wait_type waiting_tasks_count wait_time_ms max_wait_time_ms signal_wait_time_ms TRANSACTION_MUTEX 0 0 0 0
Tuttavia, il conteggio delle attività in attesa è stato incrementato sul server remoto:
wait_type waiting_tasks_count wait_time_ms max_wait_time_ms signal_wait_time_ms TRANSACTION_MUTEX 1 0 0 0
Quindi la mia aspettativa di vedere questo è stata confermata, dato che abbiamo una transazione distribuita con più di una sessione coinvolta in qualche modo con la stessa transazione.
MARS (set di risultati attivi multipli)
Che dire dell'uso di Multiple Active Result Sets (MARS)? Ci aspetteremmo anche di vedere TRANSACTION_MUTEX
accumulare quando associato all'utilizzo di MARS?
Per questo, ho utilizzato il seguente codice dell'applicazione console C# testato da Microsoft Visual Studio rispetto alla mia istanza di SQL Server 2012 e al database dei crediti. La logica di ciò che sto effettivamente facendo non è molto utile (restituisce una riga da ogni tabella), ma i lettori di dati sono sulla stessa connessione e l'attributo di connessione MultipleActiveResultSets
è impostato su true, quindi è stato sufficiente verificare se effettivamente MARS potesse guidare TRANSACTION_MUTEX
anche accumulazione:
string ConnString = @"Server=.;Database=Credit;Trusted_Connection=True;MultipleActiveResultSets=true;"; SqlConnection MARSCon = new SqlConnection(ConnString); MARSCon.Open(); SqlCommand MARSCmd1 = new SqlCommand("SELECT payment_no FROM dbo.payment;", MARSCon); SqlCommand MARSCmd2 = new SqlCommand("SELECT charge_no FROM dbo.charge;", MARSCon); SqlDataReader MARSReader1 = MARSCmd1.ExecuteReader(); SqlDataReader MARSReader2 = MARSCmd2.ExecuteReader(); MARSReader1.Read(); MARSReader2.Read(); Console.WriteLine("\t{0}", MARSReader1[0]); Console.WriteLine("\t{0}", MARSReader2[0]);
Dopo aver eseguito questo codice, ho visto il seguente accumulo per TRANSACTION_MUTEX
:
wait_type waiting_tasks_count wait_time_ms max_wait_time_ms signal_wait_time_ms TRANSACTION_MUTEX 8 2 0 0
Quindi, come puoi vedere, l'attività MARS (comunque banalmente implementata) ha causato un aumento del TRANSACTION_MUTEX
accumulo di tipo wait. E lo stesso attributo della stringa di connessione non lo guida, lo fa l'implementazione effettiva. Ad esempio, ho rimosso la seconda implementazione del lettore e ho mantenuto un singolo lettore con MultipleActiveResultSets=true
e, come previsto, non c'era TRANSACTION_MUTEX
accumulo di tempo di attesa.
Conclusione
Se vedi un TRANSACTION_MUTEX
alto attese nel tuo ambiente, spero che questo post ti dia un'idea di tre strade da esplorare:per determinare da dove provengono queste attese e se sono necessarie o meno.