Usar SELECT FOR UPDATE

En esta página se describe cómo usar la cláusula FOR UPDATE en Spanner.

Cuando usas la consulta SELECT para analizar una tabla, añade una cláusula FOR UPDATE para habilitar los bloqueos exclusivos a nivel de granularidad de fila y columna, también conocido como nivel de celda. El bloqueo se mantiene durante la vida útil de la transacción de lectura y escritura. Durante este tiempo, la cláusula FOR UPDATE impide que otras transacciones modifiquen las celdas bloqueadas hasta que se complete la transacción actual. Para obtener más información, consulta las guías de referencia de GoogleSQL y PostgreSQL FOR UPDATE.

Por qué usar la cláusula FOR UPDATE

En las bases de datos con niveles de aislamiento menos estrictos, puede que sea necesaria la cláusula FOR UPDATE para asegurarse de que una transacción simultánea no actualice los datos entre la lectura de los datos y la confirmación de la transacción. Como Spanner siempre aplica la serialización, se garantiza que la transacción solo se confirma correctamente si los datos a los que se accede en la transacción no están obsoletos en el momento de la confirmación. Por lo tanto, la cláusula FOR UPDATE no es necesaria para asegurar la corrección de las transacciones en Spanner.

Sin embargo, en los casos prácticos con una alta contención de escritura, como cuando varias transacciones leen y escriben simultáneamente en los mismos datos, las transacciones simultáneas pueden provocar un aumento de las cancelaciones. Esto se debe a que, cuando varias transacciones simultáneas adquieren bloqueos compartidos y, a continuación, intentan actualizarse a bloqueos exclusivos, las transacciones provocan un interbloqueo. A continuación, Spanner cancela todas las transacciones menos una. Para obtener más información, consulta la sección Bloqueo.

Una transacción que usa la cláusula FOR UPDATE adquiere el bloqueo exclusivo y se ejecuta, mientras que otras transacciones esperan su turno para obtener el bloqueo. Aunque Spanner puede seguir limitando el rendimiento porque las transacciones conflictivas solo se pueden realizar una a la vez, como Spanner solo está procesando una transacción, se ahorra el tiempo que se dedicaría a anular y volver a intentar las transacciones.

Por lo tanto, si es importante reducir el número de transacciones anuladas en un escenario de solicitudes de escritura simultáneas, puede usar la cláusula FOR UPDATE para reducir el número total de anulaciones y aumentar la eficiencia de la ejecución de la carga de trabajo.

Comparación con la pista LOCK_SCANNED_RANGES

La cláusula FOR UPDATE tiene una función similar a la pista LOCK_SCANNED_RANGES=exclusive.

Hay dos diferencias principales:

  • Si usas la sugerencia LOCK_SCANNED_RANGES, la transacción adquiere bloqueos exclusivos en los intervalos analizados durante toda la instrucción. No puedes adquirir bloqueos exclusivos en una subconsulta. Si usas la sugerencia de bloqueo, es posible que adquieras más bloqueos de los necesarios y que contribuyas a la contención de bloqueos en la carga de trabajo. En el siguiente ejemplo se muestra cómo usar una pista de bloqueo:

    @{lock_scanned_ranges=exclusive}
    SELECT s.SingerId, s.FullName FROM Singers AS s
    JOIN (SELECT SingerId FROM Albums WHERE MarketingBudget > 100000)
    AS a ON a.SingerId = s.SingerId;
    

    Por otro lado, puedes usar la cláusula FOR UPDATE en una subconsulta, como se muestra en el siguiente ejemplo:

    SELECT s.SingerId, s.FullName FROM Singers AS s
    JOIN (SELECT SingerId FROM Albums WHERE MarketingBudget > 100000)
    FOR UPDATE AS a ON a.SingerId = s.SingerId;
    
  • Puedes usar la sugerencia LOCK_SCANNED_RANGES en las instrucciones DML, mientras que solo puedes usar la cláusula FOR UPDATE en las instrucciones SELECT.

Semántica de bloqueo

Para reducir las solicitudes de escritura simultáneas y el coste de las transacciones que se cancelan como resultado de un interbloqueo, Spanner bloquea los datos a nivel de celda si es posible. Cuando se usa la cláusula FOR UPDATE, Spanner bloquea las celdas específicas que analiza la consulta SELECT.

En el siguiente ejemplo, la celda MarketingBudget de la fila SingerId = 1 y AlbumId = 1 está bloqueada exclusivamente en la tabla Albums, lo que impide que las transacciones simultáneas modifiquen esa celda hasta que se confirme o se revierta esta transacción. Sin embargo, las transacciones simultáneas pueden seguir actualizando la celda AlbumTitle de esa fila.

SELECT MarketingBudget
FROM Albums
WHERE SingerId = 1 and AlbumId = 1
FOR UPDATE;

Las transacciones simultáneas pueden bloquearse al leer datos bloqueados

Cuando una transacción ha adquirido bloqueos exclusivos en un intervalo analizado, las transacciones simultáneas pueden bloquear la lectura de esos datos. Spanner aplica la serializabilidad para que los datos solo se puedan leer si se garantiza que otra transacción no los ha modificado durante el tiempo de vida de la transacción. Las transacciones simultáneas que intenten leer datos ya bloqueados pueden tener que esperar hasta que se confirme o se revierta la transacción que tiene los bloqueos.

En el siguiente ejemplo, Transaction 1 bloquea las celdas MarketingBudget para 1 <= AlbumId < 5.

-- Transaction 1
SELECT MarketingBudget
FROM Albums
WHERE SingerId = 1 and AlbumId >= 1 and AlbumId < 5
FOR UPDATE;

Transaction 2, que intenta leer MarketingBudget para AlbumId = 1, se bloquea hasta que Transaction 1 se confirma o se revierte.

-- Transaction 2
SELECT MarketingBudget
FROM Albums
WHERE SingerId = 1 and AlbumId = 1;

-- Blocked by Transaction 1

Del mismo modo, una transacción que intente bloquear un intervalo analizado con FOR UPDATE se bloquea por una transacción simultánea que bloquea un intervalo analizado superpuesto.

Transaction 3 del siguiente ejemplo también está bloqueado, ya que Transaction 1 ha bloqueado las celdas MarketingBudget para 3 <= AlbumId < 5, que es el intervalo analizado superpuesto con Transaction 3.

-- Transaction 3
SELECT MarketingBudget
FROM Albums
WHERE SingerId = 1 and AlbumId >= 3 and AlbumId < 10
FOR UPDATE;

-- Blocked by Transaction 1

Leer un índice

Es posible que no se bloquee una lectura simultánea si la consulta que ha bloqueado el intervalo analizado bloquea las filas de la tabla base, pero la transacción simultánea lee de un índice.

El siguiente Transaction 1 bloquea las celdas SingerId y SingerInfo para SingerId = 1.

-- Transaction 1
SELECT SingerId, SingerInfo
FROM Singers
WHERE SingerId = 1
FOR UPDATE;

El Transaction 2 de solo lectura no está bloqueado por los bloqueos adquiridos en Transaction 1, porque consulta una tabla de índice.

-- Transaction 2
SELECT SingerId FROM Singers;

Las transacciones simultáneas no bloquean las operaciones DML en datos ya bloqueados

Cuando una transacción ha adquirido bloqueos en un intervalo de celdas con una sugerencia de bloqueo exclusivo, las transacciones simultáneas que intenten realizar una escritura sin leer los datos primero en las celdas bloqueadas pueden continuar. La transacción se bloquea en la confirmación hasta que la transacción que contiene los bloqueos se confirma o se revierte.

El siguiente Transaction 1 bloquea las celdas MarketingBudget de 1 <= AlbumId < 5.

-- Transaction 1
SELECT MarketingBudget
FROM Albums
WHERE SingerId = 1 and AlbumId >= 1 and AlbumId < 5
FOR UPDATE;

Si Transaction 2 intenta actualizar la tabla Albums, se le impide hacerlo hasta que Transaction 1 confirme o revierta la operación.

-- Transaction 2
UPDATE Albums
SET MarketingBudget = 200000
WHERE SingerId = 1 and AlbumId = 1;

> Query OK, 1 rows affected

COMMIT;

-- Blocked by Transaction 1

Las filas y los huecos se bloquean cuando se bloquea un intervalo escaneado

Cuando una transacción ha adquirido bloqueos exclusivos en un intervalo analizado, las transacciones simultáneas no pueden insertar datos en los huecos de ese intervalo.

El siguiente Transaction 1 bloquea las celdas MarketingBudget de 1 <= AlbumId < 10.

-- Transaction 1
SELECT MarketingBudget
FROM Albums
WHERE SingerId = 1 and AlbumId >= 1 and AlbumId < 10
FOR UPDATE;

Si Transaction 2 intenta insertar una fila para AlbumId = 9 que aún no existe, no podrá hacerlo hasta que Transaction 1 confirme o deshaga la operación.

-- Transaction 2
INSERT INTO Albums (SingerId, AlbumId, AlbumTitle, MarketingBudget)
VALUES (1, 9, "Hello hello!", 10000);

> Query OK, 1 rows affected

COMMIT;

-- Blocked by Transaction 1

Advertencias sobre la adquisición de bloqueos

La semántica de los bloqueos descrita proporciona directrices generales, pero no garantiza exactamente cómo se pueden adquirir los bloqueos cuando Spanner ejecuta una transacción que usa la cláusula FOR UPDATE. Los mecanismos de optimización de consultas de Spanner también pueden afectar a los bloqueos que se adquieren. La cláusula impide que otras transacciones modifiquen las celdas bloqueadas hasta que se complete la transacción actual.

Semántica de las consultas

En esta sección se ofrecen directrices sobre la semántica de las consultas al usar la cláusula FOR UPDATE.

Usar en instrucciones WITH

La cláusula FOR UPDATE no adquiere bloqueos para la instrucción WITH cuando se especifica FOR UPDATE en la consulta de nivel externo de la instrucción WITH.

En la siguiente consulta, la tabla Singers no adquiere ningún bloqueo porque la intención de bloquear no se propaga a la consulta de expresiones de tabla comunes (CTE).

WITH s AS (SELECT SingerId, SingerInfo FROM Singers WHERE SingerID > 5)
SELECT * FROM s
FOR UPDATE;

Si se especifica la cláusula FOR UPDATE en la consulta de CTE, el intervalo analizado de la consulta de CTE adquiere los bloqueos.

En el siguiente ejemplo, las celdas SingerId y SingerInfo de las filas en las que SingerId > 5 están bloqueadas.

WITH s AS
  (SELECT SingerId, SingerInfo FROM Singers WHERE SingerId > 5 FOR UPDATE)
SELECT * FROM s;

Usar en subconsultas

Puedes usar la cláusula FOR UPDATE en una consulta de nivel externo que tenga una o varias subconsultas. La consulta de nivel superior y las subconsultas adquieren bloqueos, excepto en las subconsultas de expresiones.

La siguiente consulta bloquea las celdas SingerId y SingerInfo de las filas en las que SingerId > 5.

(SELECT SingerId, SingerInfo FROM Singers WHERE SingerId > 5) AS t
FOR UPDATE;

La siguiente consulta no bloquea ninguna celda de la tabla Albums porque está dentro de una subconsulta de expresión. Las celdas SingerId y SingerInfo de las filas devueltas por la subconsulta de expresión están bloqueadas.

SELECT SingerId, SingerInfo
FROM Singers
WHERE SingerId = (SELECT SingerId FROM Albums WHERE MarketingBudget > 100000)
FOR UPDATE;

Se usa para consultar vistas.

Puedes usar la cláusula FOR UPDATE para consultar una vista, como se muestra en el siguiente ejemplo:

CREATE VIEW SingerBio AS SELECT SingerId, FullName, SingerInfo FROM Singers;

SELECT * FROM SingerBio WHERE SingerId = 5 FOR UPDATE;

No puedes usar la cláusula FOR UPDATE cuando definas una vista.

Casos prácticos no admitidos

No se admiten los siguientes casos prácticos de FOR UPDATE:

  • Como mecanismo de exclusión mutua para ejecutar código fuera de Spanner: no uses el bloqueo en Spanner para asegurar el acceso exclusivo a un recurso fuera de Spanner. Por ejemplo, Spanner puede anular transacciones si se vuelve a intentar una transacción, ya sea de forma explícita mediante código de aplicación o de forma implícita mediante código de cliente, como el controlador JDBC de Spanner. En este caso, solo se garantiza que los bloqueos se mantengan durante el intento que se haya confirmado.
  • En combinación con la sugerencia LOCK_SCANNED_RANGES: no puedes usar la cláusula FOR UPDATE y la sugerencia LOCK_SCANNED_RANGES en la misma consulta. De lo contrario, Spanner devolverá un error.
  • En las consultas de búsqueda en todo el texto: no puedes usar la cláusula FOR UPDATE en las consultas que usen índices de búsqueda en todo el texto.
  • En las transacciones de solo lectura: la cláusula FOR UPDATE solo es válida en las consultas que se ejecutan en transacciones de lectura y escritura.
  • En las instrucciones DDL: no puedes usar la cláusula FOR UPDATE en las consultas de las instrucciones DDL, que se almacenan para ejecutarse más adelante. Por ejemplo, no puedes usar la cláusula FOR UPDATE al definir una vista. Si es necesario bloquear, se puede especificar la cláusula FOR UPDATE al consultar la vista.

Siguientes pasos