En esta página, se presentan los diferentes niveles de aislamiento y se explica cómo funcionan en Spanner.
El nivel de aislamiento es una propiedad de la base de datos que define qué datos son visibles para las transacciones simultáneas. Spanner admite dos de los niveles de aislamiento definidos en el estándar ANSI/ISO de SQL: serializable y lectura repetible. Cuando creas una transacción, debes elegir el nivel de aislamiento más adecuado para ella. El nivel de aislamiento elegido permite que las transacciones individuales prioricen varios factores, como la latencia, la tasa de anulación y si la aplicación es susceptible a los efectos de las anomalías en los datos. La mejor opción depende de las demandas específicas de la carga de trabajo.
Aislamiento serializable
El aislamiento serializable es el nivel de aislamiento predeterminado en Spanner. Con el aislamiento serializable, Spanner te proporciona las garantías de control de simultaneidad más estrictas para las transacciones, lo que se denomina coherencia externa. Spanner se comporta como si todas las transacciones se ejecutaran de forma secuencial, aunque Spanner las ejecuta en varios servidores (y posiblemente en varios centros de datos) para mejorar el rendimiento y la disponibilidad en comparación con las bases de datos de un solo servidor. Además, si una transacción finaliza antes de que otra transacción comience a confirmarse, Spanner garantiza que los clientes siempre verán los resultados de las transacciones en orden secuencial. De manera intuitiva, Spanner es similar a una base de datos de una sola máquina.
La desventaja es que Spanner podría anular transacciones si una carga de trabajo tiene una alta contención de lectura y escritura, en la que muchas transacciones leen datos que otra transacción está actualizando, debido a la naturaleza fundamental de las transacciones serializables. Sin embargo, este es un buen valor predeterminado para una base de datos operativa. Te ayuda a evitar problemas de sincronización complejos que suelen surgir solo con una alta simultaneidad. Estos problemas son difíciles de reproducir y solucionar. Por lo tanto, el aislamiento serializable proporciona la mayor protección contra las anomalías de datos. Si se debe reintentar una transacción, es posible que aumente la latencia debido a los reintentos de transacción.
Aislamiento de lectura repetible
En Spanner, el aislamiento de lectura repetible se implementa con una técnica conocida comúnmente como aislamiento de instantáneas. El aislamiento de lectura repetible en Spanner garantiza que todas las operaciones de lectura dentro de una transacción vean una instantánea coherente o sólida de la base de datos tal como existía al inicio de la transacción. También garantiza que las escrituras simultáneas en los mismos datos solo se realicen correctamente si no hay conflictos. Este enfoque es beneficioso en situaciones de conflictos de lectura y escritura altos, en las que numerosas transacciones leen datos que otras transacciones podrían estar modificando. Al usar una instantánea fija, la lectura repetible evita los impactos en el rendimiento del nivel de aislamiento serializable más restrictivo. Las lecturas se pueden ejecutar sin adquirir bloqueos y sin bloquear las escrituras simultáneas, lo que genera menos transacciones anuladas que podrían tener que volver a intentarse debido a posibles conflictos de serialización. En los casos de uso en los que tus clientes ya ejecutan todo en una transacción de lectura y escritura, y es difícil rediseñar y usar transacciones de solo lectura, puedes usar el aislamiento de lectura repetible para mejorar la latencia de tus cargas de trabajo.
A diferencia del aislamiento serializable, la lectura repetible puede generar anomalías en los datos si tu aplicación depende de relaciones o restricciones de datos específicas que no se aplican en el esquema de la base de datos, en especial cuando el orden de las operaciones es importante. En estos casos, una transacción puede leer datos, tomar decisiones basadas en esos datos y, luego, escribir cambios que incumplen esas restricciones específicas de la aplicación, incluso si se siguen cumpliendo las restricciones del esquema de la base de datos. Esto sucede porque el aislamiento de lectura repetible permite que las transacciones simultáneas continúen sin una serialización estricta. Una posible anomalía se conoce como sesgo de escritura, que surge de un tipo particular de actualización simultánea, en la que cada actualización se acepta de forma independiente, pero su efecto combinado viola la integridad de los datos de la aplicación. Por ejemplo, imagina que hay un sistema hospitalario en el que al menos un médico debe estar de guardia en todo momento, y los médicos pueden solicitar que se los quite de guardia durante un turno. Con el aislamiento de lectura repetible, si tanto el Dr. Richards como la Dra. Smith están programados para estar de guardia en el mismo turno y, de forma simultánea, intentan solicitar que se los quite de la guardia, cada solicitud se completa en paralelo. Esto se debe a que ambas transacciones leen que hay al menos otro médico programado para estar de guardia al comienzo de la transacción, lo que provoca una anomalía en los datos si las transacciones se realizan correctamente. Por otro lado, el uso del aislamiento serializable evita que estas transacciones incumplan la restricción, ya que las transacciones serializables detectarán posibles anomalías en los datos y anularán la transacción. De esta manera, se garantiza la coherencia de la aplicación, ya que se aceptan tasas de anulación más altas.
En el ejemplo anterior, puedes usar la cláusula SELECT FOR UPDATE
en el aislamiento de lectura repetible.
La cláusula SELECT…FOR UPDATE
verifica si los datos que leyó en la instantánea elegida permanecen sin cambios en el momento de la confirmación. Del mismo modo, las sentencias DML y las mutaciones, que leen datos de forma interna para garantizar la integridad de las escrituras, también verifican que los datos permanezcan sin cambios en el momento de la confirmación.
Para obtener más información, consulta Usa el aislamiento de lectura repetible.
Ejemplo de caso de uso
En el siguiente ejemplo, se demuestra el beneficio de usar el aislamiento de lectura repetible para eliminar la sobrecarga de bloqueo. Tanto Transaction 1
como Transaction 2
se ejecutan en aislamiento de lectura repetible.
Transaction 1
establece una marca de tiempo de instantánea cuando se ejecuta la instrucción SELECT
.
GoogleSQL
-- Transaction 1
BEGIN;
-- Snapshot established at T1
SELECT AlbumId, MarketingBudget
FROM Albums
WHERE SingerId = 1;
/*-----------+------------------*
| AlbumId | MarketingBudget |
+------------+------------------+
| 1 | 50000 |
| 2 | 100000 |
| 3 | 70000 |
| 4 | 80000 |
*------------+------------------*/
PostgreSQL
-- Transaction 1
BEGIN;
-- Snapshot established at T1
SELECT albumid, marketingbudget
FROM albums
WHERE singerid = 1;
/*-----------+------------------*
| albumid | marketingbudget |
+------------+------------------+
| 1 | 50000 |
| 2 | 100000 |
| 3 | 70000 |
| 4 | 80000 |
*------------+------------------*/
Luego, Transaction 2
establece una marca de tiempo de instantánea después de que comienza Transaction 1
, pero antes de que se confirme. Como Transaction 1
no actualizó los datos, la consulta SELECT
en Transaction 2
lee los mismos datos que Transaction 1
.
GoogleSQL
-- Transaction 2
BEGIN;
-- Snapshot established at T2 > T1
SELECT AlbumId, MarketingBudget
FROM Albums
WHERE SingerId = 1;
INSERT INTO Albums (SingerId, AlbumId, MarketingBudget) VALUES (1, 5, 50000);
COMMIT;
PostgreSQL
-- Transaction 2
BEGIN;
-- Snapshot established at T2 > T1
SELECT albumid, marketingbudget
FROM albums
WHERE singerid = 1;
INSERT INTO albums (singerid, albumid, marketingbudget) VALUES (1, 5, 50000);
COMMIT;
Transaction 1
continúa después de que Transaction 2
se confirma.
GoogleSQL
-- Transaction 1 continues
SELECT SUM(MarketingBudget) as UsedBudget
FROM Albums
WHERE SingerId = 1;
/*-----------*
| UsedBudget |
+------------+
| 300000 |
*------------*/
PostgreSQL
-- Transaction 1 continues
SELECT SUM(marketingbudget) AS usedbudget
FROM albums
WHERE singerid = 1;
/*-----------*
| usedbudget |
+------------+
| 300000 |
*------------*/
El valor UsedBudget
que devuelve Spanner es la suma del presupuesto que leyó Transaction 1
. Esta suma solo refleja los datos presentes en la instantánea de T1
. No incluye el presupuesto que agregó Transaction 2
, ya que Transaction 2
realizó la confirmación después de que Transaction 1
estableció la instantánea T1
. Usar la lectura repetible significa que Transaction 1
no tuvo que anularse, aunque Transaction 2
modificó los datos leídos por Transaction 1
. Sin embargo, el resultado que devuelve Spanner puede ser o no el resultado esperado.
Conflictos de lectura y escritura y corrección
En el ejemplo anterior, si los datos consultados por las sentencias SELECT
en Transaction 1
se usaran para tomar decisiones posteriores sobre el presupuesto de marketing, podría haber problemas de exactitud.
Por ejemplo, supongamos que hay un presupuesto total de 400,000
. Según el resultado de la instrucción SELECT
en Transaction 1
, podríamos pensar que queda 100,000
en el presupuesto y decidir asignarlo todo a AlbumId = 4
.
GoogleSQL
-- Transaction 1 continues..
UPDATE Albums
SET MarketingBudget = MarketingBudget + 100000
WHERE SingerId = 1 AND AlbumId = 4;
COMMIT;
PostgreSQL
-- Transaction 1 continues..
UPDATE albums
SET marketingbudget = marketingbudget + 100000
WHERE singerid = 1 AND albumid = 4;
COMMIT;
Transaction 1
se confirma correctamente, aunque Transaction 2
ya asignó 50,000
del presupuesto restante de 100,000
a un álbum nuevo AlbumId = 5
.
Puedes usar la sintaxis SELECT...FOR UPDATE
para validar que ciertas lecturas de una transacción no cambien durante la vida útil de la transacción y, así, garantizar su corrección. En el siguiente ejemplo, en el que se usa SELECT...FOR UPDATE
, Transaction 1
se anula en el momento de la confirmación.
GoogleSQL
-- Transaction 1 continues..
SELECT SUM(MarketingBudget) AS TotalBudget
FROM Albums
WHERE SingerId = 1
FOR UPDATE;
/*-----------*
| TotalBudget |
+------------+
| 300000 |
*------------*/
COMMIT;
PostgreSQL
-- Transaction 1 continues..
SELECT SUM(marketingbudget) AS totalbudget
FROM albums
WHERE singerid = 1
FOR UPDATE;
/*-------------*
| totalbudget |
+-------------+
| 300000 |
*-------------*/
COMMIT;
Para obtener más información, consulta Usa SELECT FOR UPDATE en el aislamiento de lectura repetible.
Conflictos de escritura-escritura y corrección
Si se usa el nivel de aislamiento de lectura repetible, las escrituras simultáneas en los mismos datos solo se realizan correctamente si no hay conflictos.
En el siguiente ejemplo, Transaction 1
establece una marca de tiempo de instantánea en la primera instrucción SELECT
.
GoogleSQL
-- Transaction 1
BEGIN;
-- Snapshot established at T1
SELECT AlbumId, MarketingBudget
FROM Albums
WHERE SingerId = 1;
PostgreSQL
-- Transaction 1
BEGIN;
-- Snapshot established at T1
SELECT albumid, marketingbudget
FROM albums
WHERE singerid = 1;
El siguiente Transaction 2
lee los mismos datos que Transaction 1
y, luego, inserta un elemento nuevo. Transaction 2
se confirma correctamente sin esperar ni anular.
GoogleSQL
-- Transaction 2
BEGIN;
-- Snapshot established at T2 (> T1)
SELECT AlbumId, MarketingBudget
FROM Albums
WHERE SingerId = 1;
INSERT INTO Albums (SingerId, AlbumId, MarketingBudget) VALUES (1, 5, 50000);
COMMIT;
PostgreSQL
-- Transaction 2
BEGIN;
-- Snapshot established at T2 (> T1)
SELECT albumid, marketingbudget
FROM albums
WHERE singerid = 1;
INSERT INTO albums (singerid, albumid, marketingbudget) VALUES (1, 5, 50000);
COMMIT;
Transaction 1
continúa después de que Transaction 2
se confirma.
GoogleSQL
-- Transaction 1 continues
INSERT INTO Albums (SingerId, AlbumId, MarketingBudget) VALUES (1, 5, 30000);
-- Transaction aborts
COMMIT;
PostgreSQL
-- Transaction 1 continues
INSERT INTO albums (singerid, albumid, marketingbudget) VALUES (1, 5, 30000);
-- Transaction aborts
COMMIT;
Transaction 1
se anula porque Transaction 2
ya confirmó una inserción en la fila AlbumId = 5
.
¿Qué sigue?
Obtén más información para usar el nivel de aislamiento de lectura repetible.
Obtén más información para usar SELECT FOR UPDATE en el aislamiento de lectura repetible.
Obtén más información para usar SELECT FOR UPDATE en el aislamiento serializable.
Para obtener más información sobre la serialización y la coherencia externa de Spanner, consulta TrueTime y coherencia externa.