sql >> Base de Datos >  >> RDS >> Database

El nivel de aislamiento de lectura no confirmada

[ Ver el índice de toda la serie ]

Lectura no confirmada es el más débil de los cuatro niveles de aislamiento de transacciones definidos en SQL Standard (y de los seis implementados en SQL Server). Permite los tres llamados "fenómenos de concurrencia", lecturas sucias , lecturas no repetibles y fantasmas:

La mayoría de las personas que trabajan con bases de datos son conscientes de estos fenómenos, al menos en líneas generales, pero no todos se dan cuenta de que no describen completamente las garantías de aislamiento que se ofrecen; ni describen intuitivamente los diferentes comportamientos que uno puede esperar en una implementación específica como SQL Server. Más sobre eso más adelante.

Aislamiento de transacciones:la 'I' en ACID

Cada comando SQL se ejecuta dentro de una transacción (explícita, implícita o de confirmación automática). Cada transacción tiene un nivel de aislamiento asociado, que determina qué tan aislada está de los efectos de otras transacciones concurrentes. Este concepto algo técnico tiene implicaciones importantes para la forma en que se ejecutan las consultas y la calidad de los resultados que producen.

Considere una consulta simple que cuenta todas las filas de una tabla. Si esta consulta pudiera ejecutarse instantáneamente (o sin modificaciones de datos simultáneas), solo podría haber una respuesta correcta:el número de filas presentes físicamente en la tabla en ese momento. En realidad, la ejecución de la consulta llevará una cierta cantidad de tiempo y el resultado dependerá de cuántas filas encuentre realmente el motor de ejecución a medida que atraviesa cualquier estructura física que se elija para acceder a los datos.

Si las filas se agregan (o eliminan) de la tabla mediante transacciones concurrentes mientras la operación de conteo está en curso, se pueden obtener diferentes resultados dependiendo de si la transacción de conteo de filas encuentra todos, algunos o ninguno de esos cambios concurrentes, que a su vez depende del nivel de aislamiento de la transacción de conteo de filas.

Según el nivel de aislamiento, los detalles físicos y el tiempo de las operaciones concurrentes, nuestra transacción de conteo podría incluso producir un resultado que nunca fuera un fiel reflejo del estado comprometido de la tabla en ningún momento durante la transacción.

Ejemplo

Considere una transacción de conteo de filas que comienza en el tiempo T1 y escanea la tabla de principio a fin (en orden de clave de índice agrupado, por el bien del argumento). En ese momento, hay 100 filas comprometidas en la tabla. Algún tiempo después (en el tiempo T2), nuestra transacción de conteo ha encontrado 50 de esas filas. En el mismo momento, una transacción concurrente inserta dos filas en la tabla y se confirma poco tiempo después en el tiempo T3 (antes de que finalice la transacción de conteo). Una de las filas insertadas cae dentro de la mitad de la estructura de índice agrupado que nuestra transacción de conteo ya procesó, mientras que la otra fila insertada se encuentra en la parte no contada.

Cuando se complete la transacción de conteo de filas, informará 101 filas en este escenario; 100 filas inicialmente en la tabla más la única fila insertada que se encontró durante el escaneo. Este resultado no concuerda con el historial confirmado de la tabla:hubo 100 filas confirmadas en los momentos T1 y T2, luego 102 filas confirmadas en el momento T3. Nunca hubo un momento en que hubo 101 filas confirmadas.

Lo sorprendente (quizás, dependiendo de cuán profundamente haya pensado en estas cosas antes) es que este resultado es posible en el nivel de aislamiento confirmado de lectura predeterminado (bloqueo), e incluso bajo aislamiento de lectura repetible. Ambos niveles de aislamiento están garantizados para leer solo datos comprometidos, ¡pero obtuvimos un resultado que representa un estado no comprometido de la base de datos!

Análisis

El único nivel de aislamiento de transacciones que proporciona un aislamiento completo de los efectos de concurrencia es serializable. La implementación de SQL Server del nivel de aislamiento serializable significa que una transacción verá los datos comprometidos más recientes, desde el momento en que los datos se bloquearon por primera vez para el acceso. Además, se garantiza que el conjunto de datos encontrados bajo el aislamiento serializable no cambiará su membresía antes de que finalice la transacción.

El ejemplo del conteo de filas destaca un aspecto fundamental de la teoría de la base de datos:debemos tener claro qué significa un resultado "correcto" para una base de datos que experimenta modificaciones simultáneas, y debemos comprender las compensaciones que estamos haciendo al seleccionar un aislamiento. nivel más bajo que serializable.

Si necesitamos una vista puntual del estado comprometido de la base de datos, debemos usar el aislamiento de instantáneas (para garantías a nivel de transacción) o el aislamiento de instantáneas confirmadas de lectura (para garantías a nivel de declaración). Sin embargo, tenga en cuenta que una vista puntual significa que no estamos necesariamente operando en el estado comprometido actual de la base de datos; en efecto, es posible que estemos utilizando información desactualizada. Por otro lado, si estamos satisfechos con los resultados basados ​​únicamente en datos comprometidos (aunque posiblemente de diferentes puntos en el tiempo), podríamos optar por mantener el nivel de aislamiento de compromiso de lectura de bloqueo predeterminado.

Para estar seguros de producir resultados (¡y tomar decisiones!) basados ​​en el último conjunto de datos comprometidos, para un historial serial de operaciones contra la base de datos, necesitaríamos un aislamiento de transacciones serializable. Por supuesto, esta opción suele ser la más costosa en términos de uso de recursos y menor simultaneidad (incluido un mayor riesgo de interbloqueos).

En el ejemplo de conteo de filas, ambos niveles de aislamiento de instantáneas (SI y RCSI) darían un resultado de 100 filas, lo que representa el conteo de filas confirmadas al comienzo de la declaración (y la transacción en este caso). Ejecutar la consulta en bloqueo de lectura confirmada o aislamiento de lectura repetible podría producir un resultado de 100, 101 o 102 filas, según el tiempo, la granularidad de bloqueo, la posición de inserción de fila y el método de acceso físico elegido. Bajo el aislamiento serializable, el resultado sería 100 o 102 filas, según cuál de las dos transacciones simultáneas se considere ejecutada primero.

¿Qué tan malo es la lectura no confirmada?

Habiendo introducido el aislamiento de lectura no confirmada como el más débil de los niveles de aislamiento disponibles, debe esperar que ofrezca garantías de aislamiento aún más bajas que el bloqueo de lectura confirmada (el siguiente nivel de aislamiento más alto). De hecho lo hace; pero la pregunta es:¿cuán peor que bloquear el aislamiento de lectura confirmada es?

Para que comencemos con el contexto correcto, aquí hay una lista de los principales efectos de concurrencia que se pueden experimentar bajo el nivel de aislamiento confirmado de lectura de bloqueo predeterminado de SQL Server:

  • Faltan filas confirmadas
  • Filas encontradas varias veces
  • Diferentes versiones de la misma fila encontradas en un solo plan de declaración/consulta
  • Datos de columna confirmados de diferentes puntos en el tiempo en la misma fila (ejemplo)

Todos estos efectos de concurrencia se deben a la implementación de bloqueo de lectura confirmada que solo toma bloqueos compartidos a muy corto plazo al leer datos. El nivel de aislamiento de lectura no confirmada va un paso más allá, al no tomar bloqueos compartidos en absoluto, lo que da como resultado la posibilidad adicional de "lecturas sucias".

Lecturas sucias

Como recordatorio rápido, una "lectura sucia" se refiere a la lectura de datos que otra transacción simultánea está modificando (donde "cambio" incorpora operaciones de inserción, actualización, eliminación y fusión). Dicho de otra manera, una lectura sucia ocurre cuando una transacción lee datos que otra transacción ha modificado, antes de que la transacción que modifica haya confirmado o abortado esos cambios.

Ventajas y Desventajas

Las principales ventajas del aislamiento de lectura no confirmada son la reducción del potencial de bloqueo y interbloqueo debido a bloqueos incompatibles (incluido el bloqueo innecesario debido a la escalada de bloqueo) y posiblemente un mayor rendimiento (al evitar la necesidad de adquirir y liberar bloqueos compartidos).

El inconveniente potencial más obvio del aislamiento de lectura no confirmada es (como sugiere el nombre) que podríamos leer datos no confirmados (incluso datos que nunca comprometido, en el caso de una reversión de transacción). En una base de datos donde las reversiones son relativamente raras, la cuestión de leer datos no comprometidos podría verse como un mero problema de tiempo, ya que los datos en cuestión seguramente se confirmarán en algún momento, y probablemente muy pronto. Ya hemos visto inconsistencias relacionadas con el tiempo en el ejemplo de conteo de filas (que estaba operando en un nivel de aislamiento más alto), por lo que uno podría preguntarse qué tan preocupante es leer los datos "demasiado pronto".

Claramente, la respuesta depende de las prioridades locales y el contexto, pero ciertamente parece posible tomar una decisión informada para usar el aislamiento de lectura no comprometida. Sin embargo, hay más en qué pensar. La implementación de SQL Server del nivel de aislamiento de lectura no confirmada incluye algunos comportamientos sutiles que debemos tener en cuenta antes de tomar esa "elección informada".

Escaneos de órdenes de asignación

SQL Server toma el uso de aislamiento de lectura no confirmada como una señal de que estamos preparados para aceptar las inconsistencias que podrían surgir como resultado de un análisis ordenado por asignación.

Por lo general, el motor de almacenamiento solo puede elegir un análisis ordenado por asignación si se garantiza que los datos subyacentes no cambiarán. durante el análisis (porque, por ejemplo, la base de datos es de solo lectura o se especificó una sugerencia de bloqueo de tabla). Sin embargo, cuando se utiliza el aislamiento de lectura no confirmada, el motor de almacenamiento aún puede elegir un escaneo ordenado por asignación, incluso cuando los datos subyacentes pueden ser modificados por transacciones simultáneas.

En estas circunstancias, la exploración ordenada por asignación puede pasar por alto algunos datos comprometidos por completo o encontrar otros datos comprometidos más de una vez. El énfasis que hay en los comprometidos perdidos o contados dos veces datos (sin leer datos no confirmados), por lo que no es un caso de "lecturas sucias" como tal. Algunas personas consideran que esta decisión de diseño (permitir escaneos ordenados por asignación bajo aislamiento de lectura no confirmada) es bastante controvertida.

Como advertencia, debo dejar claro que el riesgo más general de perder filas comprometidas o contarlas dos veces no se limita a leer aislamiento no comprometido. Ciertamente, es posible ver efectos similares al bloquear la lectura confirmada y la lectura repetible (como vimos anteriormente), pero esto ocurre a través de un mecanismo diferente. Faltan filas confirmadas o se encuentran varias veces debido a un análisis ordenado por asignación sobre datos cambiantes es específico para usar aislamiento de lectura no confirmada.

Lectura de datos "corruptos"

Los resultados que parecen desafiar la lógica (¡e incluso comprobar las restricciones!) son posibles con el bloqueo del aislamiento de confirmación de lectura (nuevamente, consulte este artículo de Craig Freedman para ver algunos ejemplos). Para resumir, el punto es que el bloqueo de lectura confirmada puede ver datos confirmados de diferentes puntos en el tiempo, incluso para una sola fila si, por ejemplo, el plan de consulta usa técnicas como la intersección de índices.

Estos resultados pueden ser inesperados, pero están completamente en línea con la garantía de solo leer datos confirmados. Simplemente no se puede escapar del hecho de que las garantías de consistencia de datos más altas requieren niveles de aislamiento más altos.

Esos ejemplos pueden incluso ser bastante impactantes, si no los ha visto antes. Los mismos resultados son posibles bajo el aislamiento de lectura no confirmada, por supuesto, pero permitir lecturas sucias agrega una dimensión adicional:los resultados pueden incluir datos confirmados y no confirmados de diferentes puntos en el tiempo, incluso para la misma fila.

Yendo más allá, incluso es posible que una transacción de lectura no confirmada lea un valor de una sola columna en un estado mixto de datos confirmados y no confirmados. Esto puede ocurrir al leer un valor LOB (por ejemplo, xml o cualquiera de los tipos 'max') si el valor se almacena en varias páginas de datos. Una lectura no confirmada puede encontrar datos confirmados o no confirmados de diferentes puntos en el tiempo en diferentes páginas, lo que da como resultado un valor final de una sola columna que es una combinación de valores.

Para tomar un ejemplo, considere una única columna varchar(max) que inicialmente contiene 10 000 caracteres 'x'. Una transacción simultánea actualiza este valor a 10.000 caracteres 'y'. Una transacción de lectura no confirmada puede leer caracteres 'x' de una página del LOB y caracteres 'y' de otra, lo que da como resultado un valor de lectura final que contiene una combinación de caracteres 'x' e 'y'. Es difícil argumentar que esto no representa la lectura de datos "corruptos".

Demostración

Cree una tabla agrupada con una sola fila de datos LOB:

CREATE TABLE dbo.Test
(
    RowID integer PRIMARY KEY,
    LOB varchar(max) NOT NULL,
);
 
INSERT dbo.Test
    (RowID, LOB)
VALUES
    (1, REPLICATE(CONVERT(varchar(max), 'X'), 16100));

En una sesión separada, ejecute el siguiente script para leer el valor LOB en el aislamiento de lectura no confirmada:

-- Run this in session 2
SET NOCOUNT ON;
 
DECLARE 
    @ValueRead varchar(max) = '',
    @AllXs varchar(max) = REPLICATE(CONVERT(varchar(max), 'X'), 16100),
    @AllYs varchar(max) = REPLICATE(CONVERT(varchar(max), 'Y'), 16100);
 
WHILE 1 = 1
BEGIN
    SELECT @ValueRead = T.LOB
    FROM dbo.Test AS T WITH (READUNCOMMITTED)
    WHERE T.RowID = 1;
 
    IF @ValueRead NOT IN (@AllXs, @AllYs)
    BEGIN
    	PRINT LEFT(@ValueRead, 8000);
        PRINT RIGHT(@ValueRead, 8000);
        BREAK;
    END
END;

En la primera sesión, ejecute este script para escribir valores alternos en la columna LOB:

-- Run this in session 1
SET NOCOUNT ON;
 
DECLARE 
    @AllXs varchar(max) = REPLICATE(CONVERT(varchar(max), 'X'), 16100),
    @AllYs varchar(max) = REPLICATE(CONVERT(varchar(max), 'Y'), 16100);
 
WHILE 1 = 1
BEGIN
    UPDATE dbo.Test
    SET LOB = @AllYs
    WHERE RowID = 1;
 
    UPDATE dbo.Test
    SET LOB = @AllXs
    WHERE RowID = 1;
END;

Después de un breve período de tiempo, el script de la sesión dos terminará, habiendo leído un estado mixto para el valor LOB, por ejemplo:

Este problema en particular se limita a las lecturas de los valores de la columna LOB que se distribuyen en varias páginas, no debido a las garantías proporcionadas por el nivel de aislamiento, sino porque SQL Server utiliza pestillos a nivel de página para garantizar la integridad física. Un efecto secundario de este detalle de implementación es que evita tales lecturas de datos "corruptos" si los datos para una sola operación de lectura residen en una sola página.

Dependiendo de la versión de SQL Server que tenga, si se leen datos de "estado mixto" para una columna xml, obtendrá un error resultante del resultado xml posiblemente mal formado, ningún error en absoluto o el error específico no confirmado 601 , "no se pudo continuar con el escaneo con NOLOCK debido al movimiento de datos". La lectura de datos de estado mixto para otros tipos de LOB generalmente no genera un mensaje de error; la aplicación o consulta consumidora no tiene forma de saber que acaba de experimentar el peor tipo de lectura sucia. Para completar el análisis, una lectura de fila de estado mixto que no sea LOB como resultado de una intersección de índice nunca se informa como un error.

El mensaje aquí es que si usa aislamiento de lectura no confirmada, acepta que las lecturas sucias incluyen la posibilidad de leer valores LOB de estado mixto "corruptos".

La sugerencia de NOLOCK

Supongo que ninguna discusión sobre el nivel de aislamiento de lectura no confirmada estaría completa sin al menos mencionar esta sugerencia de tabla (ampliamente utilizada en exceso y mal entendida). La sugerencia en sí es solo un sinónimo de la sugerencia de tabla READUNCOMMITTED. Realiza exactamente la misma función:se accede al objeto al que se aplica mediante semántica de aislamiento de lectura no confirmada (aunque hay una excepción).

En lo que respecta al nombre "NOLOCK", simplemente significa que no se toman bloqueos compartidos al leer datos . Otros bloqueos (estabilidad del esquema, bloqueos exclusivos para la modificación de datos, etc.) aún se toman como normales.

En términos generales, las sugerencias de NOLOCK deben ser tan comunes como otras sugerencias de tabla de nivel de aislamiento por objeto, como SERIALIZABLE y READCOMMITTEDLOCK. Es decir:no muy común en absoluto, y solo se usa donde no hay una buena alternativa, un propósito bien definido y un completo comprensión de las consecuencias.

Un ejemplo de un uso legítimo de NOLOCK (o READUNCOMMITTED) es cuando se accede a DMV u otras vistas del sistema, donde un nivel de aislamiento más alto puede causar una contención no deseada en las estructuras de datos que no son de usuario. Otro ejemplo de caso límite podría ser cuando una consulta necesita acceder a una parte significativa de una tabla grande, lo que garantiza que nunca experimentará cambios en los datos mientras se ejecuta la consulta sugerida. Tendría que haber una buena razón para no usar instantáneas o leer el aislamiento de instantáneas confirmadas en su lugar, y los aumentos de rendimiento esperados deberían probarse, validarse y compararse, por ejemplo, con el uso de una única sugerencia de bloqueo de tabla compartida.

El uso menos deseable de NOLOCK es el que, lamentablemente, es el más común:aplicarlo a todos los objetos de una consulta como una especie de interruptor mágico para ir más rápido. Con la mejor voluntad del mundo, no hay mejor manera de hacer que el código de SQL Server parezca decididamente amateur. Si necesita legítimamente el aislamiento de lectura no confirmada para una consulta, un bloque de código o un módulo, probablemente sea mejor establecer el nivel de aislamiento de la sesión de manera adecuada y proporcionar comentarios para justificar la acción.

Reflexiones finales

La lectura no confirmada es una opción legítima para el nivel de aislamiento de transacciones, pero debe ser una opción informada. Como recordatorio, estos son algunos de los posibles fenómenos de simultaneidad bajo el aislamiento de compromiso de lectura de bloqueo predeterminado de SQL Server:

  • Faltan filas previamente confirmadas
  • Filas confirmadas encontradas varias veces
  • Diferentes versiones confirmadas de la misma fila encontradas en un solo plan de declaración/consulta
  • Datos confirmados de diferentes puntos en el tiempo en la misma fila (pero en columnas diferentes)
  • Lecturas de datos confirmadas que parecen contradecir las restricciones habilitadas y verificadas

Dependiendo de su punto de vista, esa podría ser una lista bastante impactante de posibles inconsistencias para el nivel de aislamiento predeterminado. A esa lista, lea el aislamiento no comprometido agrega:

  • Lecturas sucias (encontrar datos que aún no se han confirmado y que es posible que nunca se confirmen)
  • Filas que contienen una combinación de datos confirmados y no confirmados
  • Filas perdidas/duplicadas debido a exploraciones ordenadas por asignación
  • Valores LOB de estado mixto ("corruptos") individuales (columna única)
  • Error 601:"no se pudo continuar con el escaneo con NOLOCK debido al movimiento de datos" (ejemplo).

Si sus preocupaciones transaccionales principales son los efectos secundarios de bloquear el aislamiento de confirmación de lectura (bloqueo, sobrecarga de bloqueo, simultaneidad reducida debido a la escalada de bloqueo, etc.), es posible que le resulte mejor un nivel de aislamiento de versiones de fila como el aislamiento de instantáneas de confirmación de lectura (RCSI) o aislamiento de instantáneas (SI). Sin embargo, no son gratuitos y las actualizaciones bajo RCSI en particular tienen algunos comportamientos contrarios a la intuición.

Para escenarios que exigen los más altos niveles de garantías de consistencia, serializable sigue siendo la única opción segura. Para operaciones de rendimiento crítico en datos de solo lectura (por ejemplo, bases de datos grandes que son efectivamente de solo lectura entre ventanas ETL), establecer explícitamente la base de datos en READ_ONLY también puede ser una buena opción (los bloqueos compartidos no se toman cuando la base de datos está solo lectura, y no hay riesgo de inconsistencia).

También habrá un número relativamente pequeño de aplicaciones para las que el aislamiento de lectura no confirmada sea la elección correcta. Estas aplicaciones deben estar satisfechas con los resultados aproximados y la posibilidad de datos ocasionalmente inconsistentes, aparentemente inválidos (en términos de restricciones) o "posiblemente corruptos". Si los datos cambian con relativa poca frecuencia, el riesgo de estas inconsistencias también es menor.

[ Ver el índice de toda la serie ]