En esta página se presentan 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 SQL de ANSI/ISO: 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 cancelación y si la aplicación es susceptible a los efectos de las anomalías de 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 ofrece 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 en realidad las ejecuta en varios servidores (y posiblemente en varios centros de datos) para ofrecer un rendimiento y una disponibilidad superiores a los de las bases de datos de un solo servidor. Además, si una transacción se completa antes de que otra empiece a confirmarse, Spanner garantiza que los clientes siempre verán los resultados de las transacciones en orden secuencial. De forma intuitiva, Spanner es similar a una base de datos de una sola máquina.
La contrapartida es que Spanner puede 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, es un buen valor predeterminado para una base de datos operativa. Te ayuda a evitar problemas de sincronización complejos que suelen surgir solo cuando hay mucha 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 es necesario volver a intentar una transacción, puede que aumente la latencia debido a los reintentos de la transacción.
Aislamiento de lectura repetible
En Spanner, el aislamiento de lectura repetible se implementa mediante una técnica conocida como aislamiento de instantáneas. El aislamiento de lectura repetible en Spanner asegura que todas las operaciones de lectura de una transacción vean una captura de la base de datos coherente o sólida tal como estaba 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 útil en situaciones de conflictos de lectura y escritura 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 efectos en el rendimiento del nivel de aislamiento serializable, que es más restrictivo. Las lecturas se pueden ejecutar sin adquirir bloqueos y sin bloquear las escrituras simultáneas, lo que da como resultado menos transacciones anuladas que podrían tener que volver a intentarse debido a posibles conflictos de serialización. En los casos prácticos 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 provocar anomalías en los datos si tu aplicación se basa en relaciones o restricciones de datos específicas que no se aplican en el esquema de la base de datos, especialmente cuando el orden de las operaciones es importante. En estos casos, una transacción puede leer datos, tomar decisiones basadas en esos datos y, a continuación, escribir cambios que infrinjan esas restricciones específicas de la aplicación, aunque se sigan cumpliendo las restricciones del esquema de la base de datos. Esto ocurre porque el aislamiento de lectura repetible permite que las transacciones simultáneas se lleven a cabo sin una serialización estricta. Una posible anomalía es la desviación de escritura, que se produce por un tipo concreto de actualización simultánea en la que cada actualización se acepta de forma independiente, pero su efecto combinado infringe 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 les quite la guardia durante un turno. Con el aislamiento de lectura repetible, si tanto el Dr. Richards como el Dr. Smith tienen programada la misma guardia y ambos intentan simultáneamente solicitar que se les quite la guardia, cada solicitud se completará en paralelo. Esto se debe a que ambas transacciones leen que hay al menos otro médico programado para estar de guardia al inicio de la transacción, lo que provoca una anomalía en los datos si las transacciones se completan correctamente. Por otro lado, el uso del aislamiento serializable evita que estas transacciones infrinjan la restricción, ya que las transacciones serializables detectarán posibles anomalías en los datos y anularán la transacción. De este modo, se asegura la coherencia de las aplicaciones al aceptar tasas de cancelació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 ha leído en la instantánea elegida no han cambiado en el momento de la confirmación. Del mismo modo, las declaraciones de DML y las mutaciones, que leen datos internamente para asegurar la integridad de las escrituras, también verifican que los datos no cambien en el momento de la confirmación.
Para obtener más información, consulta Usar el aislamiento de lectura repetible.
Caso práctico de ejemplo
En el siguiente ejemplo se muestra la ventaja 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 la vista general 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 |
*------------+------------------*/
A continuación, Transaction 2
establece una marca de tiempo de la captura después de que Transaction 1
empiece, pero antes de que se confirme. Como Transaction 1
no ha actualizado 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
haya confirmado.
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 leído por Transaction 1
. Esta suma solo refleja los datos presentes en la
T1
instantánea. No incluye el presupuesto que ha añadido Transaction 2
, porque Transaction 2
se ha comprometido después de que Transaction 1
haya creado la instantánea T1
. Usar la lectura repetible significa que Transaction 1
no ha tenido que abortar aunque Transaction 2
haya modificado los datos leídos por Transaction 1
. Sin embargo, el resultado que devuelve Spanner puede ser el esperado o no.
Conflictos de lectura y escritura y corrección
En el ejemplo anterior, si los datos consultados por las instrucciones SELECT
de Transaction 1
se usaran para tomar decisiones posteriores sobre el presupuesto de marketing, podría haber problemas de exactitud.
Por ejemplo, supongamos que el presupuesto total es de 400,000
. Según el resultado de la instrucción SELECT
de 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 ha asignado 50,000
del presupuesto restante 100,000
a un nuevo álbum AlbumId = 5
.
Puedes usar la sintaxis SELECT...FOR UPDATE
para validar que determinadas lecturas de una transacción no cambian durante el tiempo de vida de la transacción y, de este modo, garantizar que la transacción es correcta. En el siguiente ejemplo, que usa SELECT...FOR UPDATE
, Transaction 1
se cancela 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 Usar SELECT FOR UPDATE en el aislamiento de lectura repetible.
Conflictos de escritura y corrección
Si se usa el nivel de aislamiento de lectura repetible, las escrituras simultáneas en los mismos datos solo se completarán si no hay conflictos.
En el siguiente ejemplo, Transaction 1
establece una marca de tiempo de la captura 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
e inserta un nuevo elemento. Transaction 2
se confirma correctamente sin esperar ni abortar.
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
haya confirmado.
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
aborta porque Transaction 2
ya ha insertado datos en la fila AlbumId = 5
.
Siguientes pasos
Consulta cómo usar el nivel de aislamiento de lectura repetible.
Consulta cómo usar SELECT FOR UPDATE en el aislamiento de lectura repetible.
Consulta cómo 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.