Usa SELECT FOR UPDATE

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

Cuando uses la consulta SELECT para analizar una tabla, agrega una cláusula FOR UPDATE para habilitar bloqueos exclusivos a nivel de la granularidad de filas y columnas, también conocido como nivel de celda. El bloqueo permanece en su lugar durante el ciclo de vida de la transacción de lectura y escritura. Durante este tiempo, la cláusula FOR UPDATE evita 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 FOR UPDATE de GoogleSQL y PostgreSQL.

Por qué usar la cláusula FOR UPDATE

En bases de datos con niveles de aislamiento menos estrictos, es posible que la cláusula FOR UPDATE sea necesaria para garantizar que una transacción simultánea no actualice los datos entre la lectura de los datos y la confirmación de la transacción. Dado que Spanner siempre aplica la serialización, se garantiza que la transacción solo se confirme correctamente si los datos a los que se accede dentro de la transacción no están inactivos en el momento de la confirmación. Por lo tanto, no es necesario usar la cláusula FOR UPDATE para garantizar la exactitud de las transacciones en Spanner.

Sin embargo, en casos de uso con una disputa de escritura alta, como cuando varias transacciones leen y escriben simultáneamente en los mismos datos, las transacciones simultáneas pueden causar un aumento en las anulaciones. Esto se debe a que, cuando varias transacciones simultáneas adquieren bloqueos compartidos y, luego, intentan actualizarse a bloqueos exclusivos, las transacciones provocan un interbloqueo. Luego, Spanner anula todas las transacciones, excepto una. Para obtener más información, consulta Bloqueo.

Una transacción que usa la cláusula FOR UPDATE adquiere el bloqueo exclusivo y procede a ejecutarse, mientras que otras transacciones esperan su turno para el bloqueo. Aunque Spanner aún podría limitar la capacidad de procesamiento porque las transacciones en conflicto solo se pueden realizar de a una, como Spanner solo avanza en una transacción, ahorra tiempo que, de lo contrario, se usaría en anular y reintentar transacciones.

Por lo tanto, si es importante reducir la cantidad de transacciones abortadas en una situación de solicitud de escritura simultánea, puedes usar la cláusula FOR UPDATE para reducir la cantidad total de abortos y aumentar la eficiencia de la ejecución de la carga de trabajo.

Comparación con la sugerencia LOCK_SCANNED_RANGES

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

Existen dos diferencias clave:

  • Si usas la sugerencia LOCK_SCANNED_RANGES, la transacción adquiere bloqueos exclusivos en los rangos analizados para toda la sentencia. No puedes adquirir bloqueos exclusivos en una subconsulta. El uso de la sugerencia de bloqueo puede provocar la adquisición de más bloqueos de lo necesario y contribuir a la contención de bloqueos en la carga de trabajo. En el siguiente ejemplo, se muestra cómo usar una sugerencia 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 sentencias DML, mientras que solo puedes usar la cláusula FOR UPDATE en las sentencias SELECT.

Semántica de bloqueo

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

En el siguiente ejemplo, la celda MarketingBudget en las filas SingerId = 1 y AlbumId = 1 está bloqueada de forma exclusiva en la tabla Albums, lo que evita que las transacciones simultáneas modifiquen esa celda hasta que se confirme o revierta esta transacción. Sin embargo, las transacciones simultáneas aún pueden actualizar la celda AlbumTitle en esa fila.

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

Es posible que las transacciones simultáneas bloqueen la lectura de datos bloqueados.

Cuando una transacción adquiere bloqueos exclusivos en un rango analizado, las transacciones simultáneas pueden bloquear la lectura de esos datos. Spanner aplica la serialización para que los datos solo se puedan leer si se garantiza que otra transacción no los modificará durante su ciclo de vida. Es posible que las transacciones simultáneas que intentan leer datos ya bloqueados deban esperar hasta que se confirme o revierta la transacción que mantiene 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 el MarketingBudget para AlbumId = 1, está bloqueado hasta que Transaction 1 se confirme o se revierta.

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

-- Blocked by Transaction 1

Del mismo modo, una transacción que intenta bloquear un rango analizado con FOR UPDATE está bloqueada por una transacción simultánea que bloquea un rango analizado superpuesto.

Transaction 3 en el siguiente ejemplo también está bloqueado, ya que Transaction 1 bloqueó las celdas MarketingBudget para 3 <= AlbumId < 5, que es el rango de análisis 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

Cómo leer un índice

Es posible que no se bloquee una operación de lectura simultánea si la consulta que bloqueó el rango analizado bloquea las filas de la tabla base, pero la transacción simultánea lee desde 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;

Los bloqueos adquiridos en Transaction 1 no bloquean el Transaction 2 de solo lectura, ya que consulta una tabla de índices.

-- Transaction 2
SELECT SingerId FROM Singers;

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

Cuando una transacción adquiere bloqueos en un rango de celdas con una sugerencia de bloqueo exclusivo, las transacciones simultáneas que intentan realizar una operación de escritura sin leer primero los datos en las celdas bloqueadas pueden continuar. La transacción bloquea la confirmación hasta que la transacción que contiene los bloqueos se confirme o revierta.

El siguiente 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;

Si Transaction 2 intenta actualizar la tabla Albums, se le bloquea esta acción 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 espacios existentes se bloquean cuando se bloquea un rango escaneado.

Cuando una transacción adquiere bloqueos exclusivos en un rango analizado, las transacciones simultáneas no pueden insertar datos en los espacios dentro de ese rango.

El siguiente Transaction 1 bloquea las celdas MarketingBudget para 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, se le impide hacerlo hasta que Transaction 1 confirme o revierta 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

Consideraciones sobre la adquisición de bloqueos

La semántica de bloqueos descrita proporciona orientación general, pero no es una garantía de 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 qué bloqueos 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 proporciona orientación sobre la semántica de las consultas cuando se usa la cláusula FOR UPDATE.

Uso en sentencias WITH

La cláusula FOR UPDATE no adquiere bloqueos para la sentencia WITH cuando especificas FOR UPDATE en la consulta de nivel externo de la sentencia WITH.

En la siguiente consulta, la tabla Singers no adquiere bloqueos, ya que la intención de bloqueo 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 rango 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;

Uso en subconsultas

Puedes usar la cláusula FOR UPDATE en una consulta de nivel externo que tenga una o más subconsultas. La consulta de nivel superior y las subconsultas adquieren bloqueos, excepto en las subconsultas de expresión.

La siguiente consulta bloquea las celdas SingerId y SingerInfo para 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 en la tabla Albums porque se encuentra dentro de una subconsulta de expresión. Las celdas SingerId y SingerInfo de las filas que muestra 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 defines una vista.

Casos de uso no admitidos

Los siguientes casos de uso de FOR UPDATE no son compatibles:

  • Como mecanismo de exclusión mutua para ejecutar código fuera de Spanner: No uses bloqueos en Spanner para garantizar el acceso exclusivo a un recurso fuera de Spanner. Spanner puede anular las transacciones, por ejemplo, si se vuelve a intentar una transacción, ya sea de forma explícita por el código de la aplicación o de forma implícita por el código del cliente, como el controlador JDBC de Spanner. Solo se garantiza que las cerraduras se mantengan durante el intento que se confirmó.
  • En combinación con la sugerencia LOCK_SCANNED_RANGES: No puedes usar la cláusula FOR UPDATE ni la sugerencia LOCK_SCANNED_RANGES en la misma consulta, o Spanner mostrará un error.
  • En las consultas de búsqueda en el texto completo: No puedes usar la cláusula FOR UPDATE en las consultas que usan índices de búsqueda en el texto completo.
  • En transacciones de solo lectura: La cláusula FOR UPDATE solo es válida en las consultas que se ejecutan dentro de transacciones de lectura y escritura.
  • Dentro de las declaraciones DDL: No puedes usar la cláusula FOR UPDATE en consultas dentro de las declaraciones DDL, que se almacenan para su ejecución posterior. Por ejemplo, no puedes usar la cláusula FOR UPDATE cuando defines una vista. Si se requiere el bloqueo, se puede especificar la cláusula FOR UPDATE cuando se consulta la vista.

Pasos siguientes