Usa SELECT FOR UPDATE en el aislamiento serializable

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

El mecanismo de bloqueo de la cláusula FOR UPDATE es diferente para el aislamiento de lectura repetible y el aislamiento serializable. Cuando usas la consulta SELECT para analizar una tabla con aislamiento serializable, agregar una cláusula FOR UPDATE habilita bloqueos exclusivos en la intersección del nivel de granularidad de fila y columna, también conocido como nivel de celda. El bloqueo permanece vigente durante toda 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 información sobre cómo usar la cláusula FOR UPDATE, consulta las guías de referencia de GoogleSQL y PostgreSQL de FOR UPDATE.

Por qué usar la cláusula FOR UPDATE

En las 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 aplica la serialización de forma predeterminada, se garantiza que la transacción solo se confirma correctamente si los datos a los que se accede dentro de la transacción no están desactualizados en el momento de la confirmación. Por lo tanto, la cláusula FOR UPDATE no es necesaria para garantizar la corrección de las transacciones en Spanner.

Sin embargo, en casos de uso con alta disputa de escritura, como cuando varias transacciones leen y escriben simultáneamente en los mismos datos, las transacciones simultáneas pueden provocar 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. El interbloqueo bloquea las transacciones de forma permanente porque cada una espera que la otra libere el recurso que necesita. Para avanzar, Spanner anula todas las transacciones, excepto una, para resolver el interbloqueo. Para obtener más información, consulta Bloqueo.

Una transacción que usa la cláusula FOR UPDATE adquiere el bloqueo exclusivo de forma proactiva y procede a ejecutarse, mientras que otras transacciones esperan su turno para el bloqueo. Si bien es posible que Spanner siga limitando la capacidad de procesamiento porque las transacciones en conflicto solo se pueden realizar de a una, como Spanner solo progresa con 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 anuladas en una situación de solicitud de escritura simultánea, puedes usar la cláusula FOR UPDATE para reducir la cantidad general de anulaciones 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 cumple 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 instrucción. No puedes adquirir bloqueos exclusivos en una subconsulta. Usar la sugerencia de bloqueo puede generar la adquisición de más bloqueos de los necesarios 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 declaraciones DML, mientras que solo puedes usar la cláusula FOR UPDATE en las declaraciones SELECT.

Semántica de bloqueo

Para reducir las solicitudes de escritura simultáneas y el costo de las transacciones que se anulan como resultado de un bloqueo, Spanner bloquea los datos a nivel de la celda si es posible. El nivel de celda es el nivel de datos más granular dentro de una tabla, un punto de datos en la intersección de una fila y una columna. Cuando se usa la cláusula FOR UPDATE, Spanner bloquea las celdas específicas que explora la consulta SELECT.

En el siguiente ejemplo, la celda MarketingBudget de la fila SingerId = 1 y AlbumId = 1 está bloqueada de forma exclusiva en la tabla Albums, lo que impide 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 se bloqueen al leer 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, por lo que los datos solo se pueden leer si se garantiza que otra transacción no los modificó durante la vida útil de la transacción. Es posible que las transacciones simultáneas que intenten leer datos ya bloqueados deban esperar hasta que se confirme, revierta o agote el tiempo de espera de 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, se bloquea hasta que Transaction 1 confirma o revierte la transacción.

-- 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 se bloquea por una transacción simultánea que bloquea un rango analizado superpuesto.

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

Cómo leer un índice

Es posible que no se bloquee una 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 índice.

-- Transaction 2
SELECT SingerId FROM Singers;

Las transacciones simultáneas no bloquean las operaciones DML en los 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 escritura sin leer primero los datos en las celdas bloqueadas pueden continuar. La transacción se bloquea en la confirmación hasta que la transacción que mantiene los bloqueos se confirma o revierte.

El siguiente Transaction 1 bloquea las celdas de 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 impedirá hacerlo hasta que Transaction 1 confirme o revierta la transacció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 analizado

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 de 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 bloquea hasta que Transaction 1 confirma o revierte 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 cerraduras

La semántica de bloqueo que se describe proporciona orientación general, 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 los bloqueos que se adquieren. La cláusula impide que otras transacciones modifiquen las celdas bloqueadas hasta que se complete la transacción actual.

Sintaxis de las consultas

En esta sección, se proporciona orientación sobre la sintaxis de las consultas cuando se usa la cláusula FOR UPDATE.

El uso más común es en una instrucción SELECT de nivel superior. Por ejemplo:

SELECT SingerId, SingerInfo
FROM Singers WHERE SingerID = 5
FOR UPDATE;

En este ejemplo, se muestra cómo usar la cláusula FOR UPDATE en una instrucción SELECT para bloquear de forma exclusiva las celdas SingerId y SingerInfo de WHERE SingerID = 5.

Uso en sentencias WITH

La cláusula FOR UPDATE no adquiere bloqueos para la instrucción WITH cuando especificas 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, 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 del CTE, el rango analizado de la consulta del 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 devuelve 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 el bloqueo en Spanner para garantizar el acceso exclusivo a un recurso fuera de Spanner. Spanner puede anular las transacciones, por ejemplo, si se reintenta 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 exclusiones 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 y la sugerencia LOCK_SCANNED_RANGES en la misma consulta. De lo contrario, Spanner devolverá 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.
  • En las declaraciones DDL: No puedes usar la cláusula FOR UPDATE en las 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 un bloqueo, se puede especificar la cláusula FOR UPDATE cuando se consulta la vista.

Pasos siguientes