En SQL Server, ¿cómo funcionan los bloqueos de lectura?

17

Supongamos que tengo la siguiente consulta de larga duración

UPDATE [Table1]
SET [Col1] = 'some value'
WHERE [Col2] -- some clause which selects thousands of rows

y supongamos que la siguiente consulta se ejecuta mientras se ejecuta la consulta anterior

SELECT *
FROM [Table1]

¿La primera consulta evita que la segunda consulta se ejecute hasta que se complete la primera consulta? Si es así, ¿la primera consulta evita que la segunda consulta se ejecute en todas las filas o solo en las filas involucradas en la cláusula WHERE?

EDITAR:

Supongamos que la segunda consulta es

SELECT [Col1], [Col2]
FROM [Table1]
WHERE [Col2] -- some clause whose matching elements overlap those from
             -- the clause in the first query and which has additional matching elements
cm007
fuente

Respuestas:

14

Le recomiendo que lea Comprendiendo cómo SQL Server ejecuta una consulta , tiene una explicación de cómo funcionan las lecturas y escrituras y cómo funciona el bloqueo.

La vista de 10000 pies es la siguiente:

  • los operadores de lectura adquieren bloqueos compartidos en los datos que leen, antes de leer los datos
  • los operadores de escritura adquieren bloqueos exclusivos en los datos que modifican antes de modificar los datos
  • los bloqueos de datos son solo cadenas, por ejemplo. un hash de la clave que se lee en el ámbito de la base de datos y el objeto.
  • el administrador de bloqueos mantiene una lista de todos los bloqueos otorgados y detecta incompatibilidades, de acuerdo con la matriz de compatibilidad de bloqueos
  • las solicitudes incompatibles se suspenden hasta que se libera la concesión incompatible que las bloquea
  • los operadores usan una jerarquía de bloqueo para declarar la intención de leer o actualizar datos en el nivel superior (nivel de página o tabla, ignorando las opciones de nivel de partición). Esto permite a los operadores bloquear tablas enteras sin bloquear cada fila individual
  • la vida útil de la cerradura y las cerraduras de rango se utilizan para imponer niveles de aislamiento más altos

Esto es realmente solo la punta del iceberg. El tema es vasto. En su ejemplo, nadie puede responder a su pregunta sobre qué se está bloqueando realmente porque dependerá de muchos factores. Por supuesto, ninguna aplicación debería emitir un SELECT * FROM Table1 porque le falta una cláusula WHERE y está usando *. Estas son malas prácticas porque, entre otras cosas, conducirán exactamente a la contención de bloqueo.

Si encuentra bloqueos de lectura frente a escritura, debe buscar versiones de fila y aislamiento de instantáneas. Lea los niveles de aislamiento basados ​​en el control de versiones de filas .

Remus Rusanu
fuente
¿Qué sucede si necesito todo el contenido de una tabla (digamos que tengo solo 14 filas)? ¿Cómo es una mala práctica SELECT * FROM Table1si eso es exactamente lo que necesito?
Azimuth
1
*Por sí solo es una mala práctica porque cuando la estructura de la tabla cambia, la aplicación generalmente se rompe (en el resultado aparecen columnas inesperadas).
Remus Rusanu
3

Editar: como señala @MaxVernon , lo siguiente no es una sugerencia para usar NOLOCK , y muy bien debería haber mencionado establecer el nivel de transacción READ UNCOMMITEDy dejar que la connotación negativa permanezca allí en lugar de NOLOCKaparecer en primer lugar. Entonces, como se publicó originalmente:

La respuesta rápida y simple es "Sí, la primera consulta bloqueará la segunda consulta a menos que se especifique una pista de índice específica ( NOLOCK , a veces llamada" lectura sucia ") o el nivel de aislamiento de transacción de la segunda consulta se establece en READ UNCOMMITED(que opera de manera idéntica), No, no lo hace."

En respuesta al detalle adicional proporcionado en la pregunta que implica la inclusión de una WITHcláusula sobre la segunda SELECT, que es mutuamente excluyente o no, las interacciones entre las dos consultas serán en gran medida las mismas.

IF NOT EXISTS ( SELECT  1
                FROM    sys.objects
                WHERE   name = 'Foo'
                    AND type = 'U' )
BEGIN
    --DROP TABLE dbo.Foo;
    CREATE TABLE dbo.Foo
    (
        Foo_PK          BIGINT IDENTITY( 1, 1 ) NOT NULL,
                            PRIMARY KEY ( Foo_PK ),
        Bar             BIT,
        x               BIT,
        y               BIT,
        z               BIT
    );

    CREATE NONCLUSTERED INDEX IX_Foo_x
        ON  dbo.Foo ( x );

    INSERT INTO dbo.Foo ( Bar, x, y, z )
    VALUES ( 1, 1, 1, 1 ), ( 0, 0, 0, 0 );
END;    
GO

BEGIN TRANSACTION;

UPDATE  dbo.Foo
    SET y = 0
WHERE   x = 1;

-- COMMIT TRANSACTION;

En una sesión separada, ejecute lo siguiente:

SELECT  *
FROM    dbo.Foo WITH ( NOLOCK );
GO

SELECT  *
FROM    dbo.Foo;

Puede examinar los bloqueos que se están ejecutando actualmente sp_lock, preferiblemente en otra sesión separada:

EXECUTE dbo.sp_lock;

Debería ver un KEYbloqueo de tipo retenido por el spid que realiza la transacción de inserción en modo X(exclusivo), que no debe confundirse con los otros IXbloqueos (Intent-Exclusive). La documentación del bloqueo indica que, si bien el KEYbloqueo es específico del rango, también evita que otras transacciones inserten o actualicen las columnas afectadas al alterar los datos contenidos en el mismo para que pueda caer dentro de ese rango de la consulta original. Como el bloqueo en sí es exclusivo, la primera consulta impide el acceso al recurso desde cualquier otra transacción concurrente. En efecto, todas las filas de la columna están bloqueadas, estén o no dentro del rango especificado por la primera consulta.

El Sbloqueo retenido por la segunda sesión se mantendrá así WAIThasta que se Xlibere el bloqueo, evitando que se tome otro X(o U) bloqueo en ese recurso desde un spid concurrente diferente antes de que la segunda sesión complete su operación de lectura, justificando la existencia del Sbloqueo.

Ahora una edición para mayor claridad: a menos que me equivoque en lo que es una lectura sucia de la breve descripción de los riesgos mencionados aquí ... Edición 3 : Acabo de darme cuenta de que no estoy considerando el efecto de un punto de verificación de antecedentes que escribe un de transacciones aún no confirmadas en disco, así que sí, mi explicación fue engañosa.

En la segunda consulta, el primer lote puede (y en este caso) devolverá datos no confirmados. El segundo lote, que se ejecuta en el nivel de aislamiento de transacción predeterminado READ COMMITED, regresará solo después de que se haya completado una confirmación o reversión en la primera sesión.

Desde aquí puede ver sus planes de consulta y los niveles de bloqueo asociados, pero mejor aún, puede leer todo acerca de los bloqueos en SQL Server aquí .

Avarkx
fuente
1
Una advertencia sobre el uso WITH (NOLOCK)sería útil en este caso. Visite brentozar.com/archive/2011/11/… y brentozar.com/archive/2013/02/… para obtener más información.
Max Vernon
3
Ah, la WITH (NOLOCK)sugerencia no devuelve páginas sucias de la memoria que no se han confirmado. En realidad, lee las filas de la tabla (ya sea en el disco o en la memoria caché) sin bloquear que los escritores actualicen o agreguen filas a las páginas que usa la tabla.
Max Vernon
2
Estoy confundido. Si la respuesta a "¿la primera consulta impide que se ejecute la segunda?" es "No", ¿cómo puede la respuesta a la segunda pregunta ser "Sí"? ¿Puedes aclarar qué preguntas estás respondiendo y ampliar tus respuestas?
Jon of All Trades
Ediciones en abundancia, lo siento amigos! ¡Avíseme si hay algo más que no esté claro!
Avarkx