Recientemente encontré un alto TRANSACTION_MUTEX
tiempo de espera acumulado en un sistema cliente. No podía recordar un caso en el que vi este tipo de espera cerca de la parte superior de la lista de "esperas altas" y tenía curiosidad acerca de qué factores podrían impulsar este tipo de tiempo de espera general.
La definición de Books Online de TRANSACTION_MUTEX
es que "ocurre durante la sincronización del acceso a una transacción por múltiples lotes". No hay muchas áreas dentro del motor de SQL Server que expongan este tipo de funcionalidad, por lo que mi investigación se redujo a las siguientes tecnologías:
- El obsoleto
sp_getbindtoken
ysp_bindsession
procedimientos almacenados del sistema utilizados para manejar conexiones vinculadas - Transacciones distribuidas
- MARS (conjuntos de resultados activos múltiples)
Mi objetivo era probar cada tecnología y ver si influía en TRANSACTION_MUTEX
tipo de espera.
La primera prueba que realicé usó el obsoleto sp_getbindtoken
y sp_bindsession
procedimientos almacenados. El sp_getbindtoken
devuelve un identificador de transacción que luego puede ser utilizado por sp_bindsession
para vincular varias sesiones en la misma transacción.
Antes de cada escenario de prueba, me aseguré de borrar las estadísticas de espera de mi instancia de SQL Server de prueba:
DBCC SQLPERF('waitstats', CLEAR); GO
Mi instancia de prueba de SQL Server estaba ejecutando SQL Server 2012 SP1 Developer Edition (11.0.3000). Utilicé la base de datos de ejemplo de crédito, aunque podría usar cualquier otro tipo de base de datos de ejemplo como AdventureWorks si quisiera, ya que el esquema y la distribución de datos no son directamente relevantes para el tema de este artículo y no eran necesarios para conducir el TRANSACTION_MUTEX
tiempo de espera.
sp_getbindtoken/sp_bindsession
En la ventana de la primera sesión de SQL Server Management Studio, ejecuté el siguiente código para comenzar una transacción y generar el token de vinculación para el alistamiento de las otras sesiones planificadas:
USE Credit; GO BEGIN TRANSACTION; DECLARE @out_token varchar(255); EXECUTE sp_getbindtoken @out_token OUTPUT; SELECT @out_token AS out_token; GO
Esto devolvió un @out_token
de S/Z5_GOHLaGY<^i]S9LXZ-5---.fE---
. En dos ventanas de consulta separadas de SQL Server Management Studio, ejecuté el siguiente código para unirme a las sesiones existentes (accediendo a la transacción compartida):
USE Credit; GO EXEC sp_bindsession 'S/Z5_GOHLaGY<^i]S9LXZ-5---.fE---';
Y con la ventana de la primera sesión aún abierta, inicié el siguiente bucle para actualizar la tabla de la tabla de cargos con una fecha de cargo igual a la fecha y hora actuales, y luego ejecuté la misma lógica en las otras dos ventanas (tres sesiones activas en la bucle):
WHILE 1 = 1 BEGIN UPDATE dbo.charge SET charge_dt = SYSDATETIME(); END
Después de unos segundos, cancelé cada consulta en ejecución. De las tres sesiones, solo una pudo realizar actualizaciones, aunque las otras dos sesiones se unieron activamente a la misma transacción. Y si miro el TRANSACTION_MUTEX
tipo de espera, puedo ver que efectivamente se incrementó:
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';
Los resultados de esta prueba en particular fueron los siguientes:
wait_type waiting_tasks_count wait_time_ms max_wait_time_ms signal_wait_time_ms TRANSACTION_MUTEX 2 181732 93899 0
Así que veo que había dos tareas en espera (las dos sesiones que simultáneamente intentaban actualizar la misma tabla a través del bucle). Como no había ejecutado SET NOCOUNT ON
, pude ver que solo el primero ejecutó UPDATE
loop obtuvo cambios. Probé esta técnica similar usando algunas variaciones diferentes (por ejemplo, cuatro sesiones generales, con tres en espera) y el TRANSACTION_MUTEX
incrementando mostró un comportamiento similar. También vi el TRANSACTION_MUTEX
acumulación al actualizar simultáneamente una tabla diferente para cada sesión, por lo que no se requirieron modificaciones contra el mismo objeto en un bucle para reproducir el TRANSACTION_MUTEX
acumulación de tiempo de espera.
Transacciones distribuidas
Mi próxima prueba consistió en ver si TRANSACTION_MUTEX
el tiempo de espera se incrementó para las transacciones distribuidas. Para esta prueba, utilicé dos instancias de SQL Server y un servidor vinculado conectado entre los dos. MS DTC se estaba ejecutando y configurado correctamente, y ejecuté el siguiente código que realizó un DELETE
local y un DELETE
remoto a través del servidor vinculado y luego deshace los cambios:
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;
La TRANSACTION_MUTEX
no mostró actividad en el servidor local:
wait_type waiting_tasks_count wait_time_ms max_wait_time_ms signal_wait_time_ms TRANSACTION_MUTEX 0 0 0 0
Sin embargo, el recuento de tareas en espera se incrementó en el servidor remoto:
wait_type waiting_tasks_count wait_time_ms max_wait_time_ms signal_wait_time_ms TRANSACTION_MUTEX 1 0 0 0
Así que mi expectativa de ver esto se confirmó, dado que tenemos una transacción distribuida con más de una sesión involucrada de alguna manera con la misma transacción.
MARS (Conjuntos de resultados activos múltiples)
¿Qué pasa con el uso de conjuntos de resultados activos múltiples (MARS)? ¿También esperaríamos ver TRANSACTION_MUTEX
acumulan cuando se asocian con el uso de MARS?
Para esto, utilicé el siguiente código de aplicación de consola C# probado desde Microsoft Visual Studio contra mi instancia de SQL Server 2012 y la base de datos de crédito. La lógica de lo que realmente estoy haciendo no es muy útil (devuelve una fila de cada tabla), pero los lectores de datos están en la misma conexión y el atributo de conexión MultipleActiveResultSets
se establece en verdadero, por lo que fue suficiente para verificar si MARS podría conducir TRANSACTION_MUTEX
acumulación también:
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]);
Después de ejecutar este código, vi la siguiente acumulación de TRANSACTION_MUTEX
:
wait_type waiting_tasks_count wait_time_ms max_wait_time_ms signal_wait_time_ms TRANSACTION_MUTEX 8 2 0 0
Entonces, como puede ver, la actividad de MARS (aunque se haya implementado de manera trivial) provocó un aumento en TRANSACTION_MUTEX
acumulación de tipo de espera. Y el atributo de cadena de conexión en sí mismo no impulsa esto, lo hace la implementación real. Por ejemplo, eliminé la implementación del segundo lector y solo mantuve un único lector con MultipleActiveResultSets=true
, y como era de esperar, no hubo TRANSACTION_MUTEX
acumulación de tiempo de espera.
Conclusión
Si está viendo un nivel alto de TRANSACTION_MUTEX
esperas en su entorno, espero que esta publicación le brinde una idea de tres vías para explorar:para determinar de dónde provienen estas esperas y si son necesarias o no.