Una de las correcciones incluidas en la Actualización acumulativa 11 para SQL Server 2008 R2 Service Pack 2 aborda un "interbloqueo incorrecto" que puede ocurrir en un escenario específico (explicado más adelante en este artículo). Desafortunadamente, la solución presenta un nuevo error, donde las consultas SELECT bajo RCSI (aislamiento de instantáneas confirmadas de lectura) comienzan a tomar bloqueos de intención compartida a nivel de tabla. Como consecuencia, es posible que vea un mayor bloqueo (y potencialmente interbloqueos) para consultas RCSI después de aplicar 2008 R2 SP2 CU11 (o posterior).
Esto será una sorpresa desagradable para cualquiera que esté acostumbrado a que los lectores no bloqueen a los escritores (y viceversa) al usar RCSI. No hay solución para el error de RCSI en el momento de escribir este artículo. De hecho, el elemento Connect creado por Eugene Karpovich para informar el problema se cerró como "No se solucionará", aunque entiendo que esta decisión se encuentra actualmente en revisión.
Por lo general, este problema podría no ser una gran preocupación, ya que las actualizaciones acumulativas generalmente no se aplican tan ampliamente como los paquetes de servicio completos. Sin embargo, Microsoft anunció recientemente que habrá un Service Pack 3 final para SQL Server 2008 R2. Este Service Pack será un simple resumen de las actualizaciones acumulativas de SP2 existentes (hasta CU13 inclusive), pero sin nuevas correcciones. El resultado de todo esto es que, a menos que algo cambie mientras tanto, los usuarios que apliquen SP3 de repente comenzarán a verse afectados por el error RCSI introducido en CU11.
editar:Justo antes de que se publicara este artículo, Microsoft confirmó que esta regresión se corregirá en el SP3.
El mismo error de "interbloqueo incorrecto" (cuya corrección presenta el nuevo error) también se corrigió en la Actualización acumulativa 8 para SQL Server 2012 Service Pack 1, como se describe en KB2923460. La solución para SQL Server 2012 es diferente y no introducir el nuevo problema RCSI.
SQL Server 2014 nunca se vio afectado por ningún problema, por lo que puedo decir. Ciertamente, no hay documentación que indique lo contrario, y las pruebas que realicé en 2014 RTM, CU1 y CU2 no reproducen ninguno de los dos errores.
El error RCSI R2 de 2008
Una consulta SELECT que se ejecuta bajo RCSI generalmente toma solo un bloqueo de estabilidad de esquema (Sch-S), que es compatible con todos los demás bloqueos, excepto un bloqueo de modificación de esquema (Sch-M). Cuando se aplica CU11 (o posterior) a una instancia de SQL Server 2008 R2, estas consultas comienzan a tomar un bloqueo de intención compartida (Tab-IS) a nivel de tabla. El siguiente script de prueba se puede utilizar para demostrar la diferencia en los comportamientos:
USE master; GO SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SET NOCOUNT ON; GO CREATE DATABASE RCSI; GO ALTER DATABASE RCSI SET READ_COMMITTED_SNAPSHOT ON; GO ALTER DATABASE RCSI SET ALLOW_SNAPSHOT_ISOLATION OFF; GO USE RCSI; GO CREATE TABLE dbo.Test ( id integer IDENTITY NOT NULL, col1 integer NOT NULL, CONSTRAINT PK_Test PRIMARY KEY CLUSTERED (id) ); GO INSERT dbo.Test (col1) VALUES (1), (2), (3), (4); GO -- Show locks DBCC TRACEON (1200, 3604, -1) WITH NO_INFOMSGS; SELECT * FROM dbo.Test; DBCC TRACEOFF (1200, 3604, -1) WITH NO_INFOMSGS; GO ALTER DATABASE RCSI SET SINGLE_USER WITH ROLLBACK IMMEDIATE; USE master; DROP DATABASE RCSI;
Cuando se ejecuta contra una instancia de SQL Server 2008 R2 sin el error, la salida de depuración muestra un solo bloqueo Sch-S tomado para la declaración de prueba como se esperaba:
Proceso de adquisición de bloqueo Sch-S en OBJECT:7:2105058535:0 resultado:OKProceso de liberación de bloqueo en OBJECT:7:2105058535:0
Cuando se ejecuta contra SQL Server 2008 R2 compilación 10.50.4302 (o superior), el resultado es similar a:
Proceso de adquisición de bloqueo IS en OBJECT:7:2105058535:0 resultado:OKProceso de liberación de bloqueo en OBJECT:7:2105058535:0
Observe que el candado Sch-S ha sido reemplazado por un candado Tab-IS.
Implicaciones y mitigaciones
Un bloqueo de intención compartida (IS) sigue siendo un bloqueo muy compatible, pero no es tan compatible con la simultaneidad como Sch-S. La matriz de compatibilidad de bloqueos muestra que un bloqueo IS entra en conflicto con:
- Sch-M (modificación de esquema) – según Sch-S
- BU (actualización masiva)
- X (exclusivo)
La incompatibilidad con bloqueos exclusivos (X) significa que una lectura bajo RCSI se bloqueará si un proceso simultáneo tiene un bloqueo exclusivo en el mismo recurso. Del mismo modo, un escritor que necesite un bloqueo exclusivo se bloqueará si un lector RCSI simultáneo tiene un bloqueo IS. Se obtienen bloqueos exclusivos cada vez que se modifican los datos y se mantienen hasta el final de la transacción, por lo que el efecto del error es que los lectores bajo RCSI serán bloqueados por escritores simultáneos (y viceversa) cuando no lo estaban antes de que se aplicara CU11.
Un factor atenuante significativo es que el error solo provoca un nivel de tabla bloqueo de intención compartida que se tomará. Un escritor concurrente que necesita un nivel de tabla el bloqueo exclusivo provocará un bloqueo (y potencialmente un interbloqueo). Sin embargo, los escritores simultáneos que solo requieren bloqueos exclusivos en un nivel inferior (por ejemplo, fila, página o partición) no causar un bloqueo o un interbloqueo. A nivel de tabla, estos escritores solo adquirirán un bloqueo de intención exclusiva (IX), que es compatible con Tab-IS. Los bloqueos exclusivos tomados en niveles más bajos de granularidad no causarán un conflicto.
En la mayoría de los sistemas, los bloqueos exclusivos a nivel de tabla (Tab-X) serán relativamente poco comunes. A menos que se solicite explícitamente mediante una sugerencia TABLOCKX, algunas posibles causas de un bloqueo de Tab-X son:
- Bloquear la escalada desde una granularidad más baja
- Uso de SERIALIZABLE sin un índice de soporte para bloqueos de rango de claves
Una solución técnica es agregar la sugerencia de tabla (redundante) WITH (READCOMMITTED)
a cada tabla en cada consulta que se ejecuta bajo RCSI. Esto sucede para evitar el error, por lo que solo se toma un bloqueo Sch-S, pero no es una propuesta práctica.
A pesar de estas mitigaciones, tomar Tab-IS para una consulta de solo lectura en RCSI sigue siendo un comportamiento incorrecto. Espero que se pueda arreglar para SQL Server 2008 R2 antes de que se lance el Service Pack 3.
El error "Interbloqueo incorrecto"
Como se mencionó anteriormente, el error RCSI se presenta como un efecto secundario de una solución para un error de "interbloqueo incorrecto". Este problema anterior está documentado para SQL Server 2008 R2 en KB2929464 y para SQL Server 2012 en KB2923460. Ninguno de los documentos es un modelo de claridad (o precisión), pero el problema subyacente es bastante interesante, por lo que quiero dedicar un poco de tiempo a verlo aquí.
Esencialmente, el interbloqueo ocurre cuando:
- Tres o más transacciones simultáneas leídas de la misma tabla
- Las sugerencias BLOQUEOUPD y BLOQUEOTAB se utilizan en los tres casos
- La configuración de la base de datos READ_COMMITTED_SNAPSHOT está ACTIVADA
Tenga en cuenta que no importa en qué nivel de aislamiento se ejecuten las transacciones. Para reproducir el error, primero ejecute el siguiente script de instalación:
USE master; GO SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO CREATE DATABASE IncorrectDeadlock; GO ALTER DATABASE IncorrectDeadlock SET READ_COMMITTED_SNAPSHOT ON; GO USE IncorrectDeadlock; GO CREATE TABLE dbo.Test ( id integer IDENTITY NOT NULL, col1 integer NOT NULL, CONSTRAINT PK_Test PRIMARY KEY CLUSTERED (id) ); GO INSERT dbo.Test (col1) VALUES (1);
A continuación, ejecute el siguiente script en tres conexiones separadas (tenga en cuenta que la transacción se deja abierta):
USE IncorrectDeadlock; GO SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO BEGIN TRANSACTION; SELECT T.id, T.col1 FROM dbo.Test AS T WITH (UPDLOCK, TABLOCK);
En este punto, la primera sesión habrá devuelto un conjunto de resultados y las otras dos estarán bloqueadas. El "interbloqueo incorrecto" surge cuando la primera sesión completa su transacción (ya sea confirmando o retrocediendo). Cuando esto ocurre, una de las otras dos sesiones informará un interbloqueo:
El interbloqueo se produce porque las dos sesiones previamente bloqueadas tienen Tab-IX (exclusivo de intención de nivel de tabla) y ambas quieren convertir su bloqueo a Tab-X (exclusivo de nivel de tabla). Tab-IX es compatible con otra Tab-IX, pero no con Tab-X. Este es un punto muerto de conversión (y la ironía aquí es que UPDLOCK se usa a menudo para evitar puntos muertos de conversión).
Siéntase libre de variar el nivel de aislamiento de transacciones para las tres consultas como desee. El interbloqueo ocurrirá independientemente, siempre que RCSI esté habilitado, con los mismos bloqueos involucrados. Cuando se completen las pruebas, elimine la base de datos de prueba:
USE IncorrectDeadlock; ALTER DATABASE IncorrectDeadlock SET SINGLE_USER WITH ROLLBACK IMMEDIATE; USE master; DROP DATABASE IncorrectDeadlock;
Análisis y Explicación
Personalmente, no recuerdo haber usado nunca UPDLOCK y TABLOCK juntos en mi código. Para mí, esta combinación de sugerencias parece intuitivamente extraña porque SQL Server no tiene un bloqueo de actualización a nivel de tabla . Entonces, ¿qué significa siquiera significar especificar juntas las sugerencias de BLOQUEOUP y BLOQUEO?
La documentación tiene esto que decir:
UPDLOCKEspecifica que los bloqueos de actualización se tomarán y se mantendrán hasta que se complete la transacción. UPDLOCK toma bloqueos de actualización para operaciones de lectura solo a nivel de fila o de página. Si UPDLOCK se combina con TABLOCK, o si se toma un bloqueo a nivel de tabla por alguna otra razón, se tomará un bloqueo exclusivo (X) en su lugar.
Esto sugiere que la combinación de sugerencias debería dar como resultado un único bloqueo de tabla exclusivo. De hecho, esta no es toda la historia:
En SQL Server 2000, la combinación de sugerencias de UPDLOCK y TABLOCK da como resultado que se tome Tab-S (un bloqueo de tabla compartido) seguido de la conversión a Tab-X (bloqueo de tabla exclusivo) en todos los niveles de aislamiento, excepto LECTURA NO COMPROMETIDA. Esta secuencia de bloqueos puede resultar en un interbloqueo en el que están involucradas tres o más sesiones:dos sesiones adquieren Tab-S y ambas esperan que la otra se convierta a Tab-X. En LECTURA NO COMPROMETIDA, SQL Server 2000 toma Sch-S y luego Tab-X, que no es propenso a interbloqueos (solo bloqueo normal).
En SQL Server 2005 en adelante (sin la corrección de errores), los bloqueos tomados dependen solo sobre si RCSI está habilitado o no. Si RCSI está habilitado, todos los niveles de aislamiento tome Tab-IX y luego conviértalo a Tab-X. Esta secuencia provoca el interbloqueo de las direcciones de corrección de errores.
Si RCSI no está habilitado, los niveles de aislamiento coincidentes se comportan como lo hacían en SQL Server 2000 (tomando Tab-S y luego convirtiéndolos a Tab-X). El nivel de aislamiento de instantáneas (nuevo para 2005) toma Sch-S seguido de Tab-X. Como consecuencia, SI y READ UNCOMMITTED son los únicos niveles de aislamiento que no son propensos a este interbloqueo en el escenario UPDLOCK, TABLOCK cuando RCSI no está habilitado.
La corrección del punto muerto
La solución cambia los bloqueos tomados cuando UPDLOCK y TABLOCK se especifican juntos, para todos los niveles de aislamiento , y independientemente de si RCSI está habilitado o no. Después de aplicar la corrección, UPDLOCK y TABLOCK hacen que el motor adquiera Tab-SIX (nivel de tabla compartido con intención exclusiva), que luego se convierte en Tab-X.
Esto evita el escenario de interbloqueo porque Tab-SIX es incompatible con otro Tab-SIX. Recuerde, el interbloqueo ocurrió cuando dos procesos retuvieron Tab-IX esperando para convertirse a Tab-X. Con Tab-IX reemplazada por Tab-SIX, no es posible que ambos tengan Tab-SIX al mismo tiempo. El resultado es un escenario de bloqueo normal en lugar de un punto muerto.
Reflexiones finales
La solución de "interbloqueo incorrecto" resuelve un escenario de interbloqueo en particular, pero aún así no da como resultado el comportamiento que imagino que las personas que especificaron UPDLOCK y TABLOCK previeron. Si SQL Server tuviera un bloqueo Tab-U (actualización a nivel de tabla), evitaría cambios simultáneos en la tabla pero permitiría lectores simultáneos. Esto es lo que imagino que sería la intención de las personas que usan estas sugerencias juntas, y puedo ver cómo podría ser útil.
La implementación actual (donde finalmente se toma Tab-X en lugar de Tab-U que falta) no coincide con esta expectativa porque Tab-X evita las lecturas simultáneas (a menos que se use un nivel de aislamiento de versiones de fila). También podríamos especificar TABLOCKX en muchos casos. El hecho de que la solución también presente un nuevo error (solo para usuarios de SQL Server 2008 R2) también es desafortunado, especialmente si el error se incluye en 2008 R2 SP3.
Tenga en cuenta que la corrección de puntos muertos no está disponible para las versiones de SQL Server anteriores a 2008 R2. Estas versiones seguirán teniendo el comportamiento de bloqueo complejo para UPDLOCK y TABLOCK como se describe anteriormente.
Mi agradecimiento a Eugene Karpovich, quien primero me llamó la atención sobre este problema en un comentario a mi artículo sobre Modificaciones de datos bajo RCSI.