Cette page présente différents niveaux d'isolation et explique comment ils fonctionnent dans Spanner.
Le niveau d'isolation est une propriété de base de données qui définit les données visibles par les transactions simultanées. Spanner est compatible avec deux des niveaux d'isolation définis dans la norme SQL ANSI/ISO : sérialisable et lecture reproductible. Lorsque vous créez une transaction, vous devez choisir le niveau d'isolation le plus approprié. Le niveau d'isolation choisi permet aux transactions individuelles de hiérarchiser différents facteurs tels que la latence, le taux d'abandon et la sensibilité de l'application aux effets des anomalies de données. Le meilleur choix dépend des exigences spécifiques de la charge de travail.
Isolation sérialisable
L'isolation sérialisable est le niveau d'isolation par défaut dans Spanner. Avec l'isolation sérialisable, Spanner vous offre les garanties les plus strictes en matière de contrôle de simultanéité pour les transactions, appelée cohérence externe. Spanner se comporte comme si toutes les transactions étaient exécutées de manière séquentielle, même si Spanner les exécute sur plusieurs serveurs (et éventuellement dans plusieurs centres de données) à des fins de performances et de disponibilité accrues par rapport aux bases de données à serveur unique. De plus, si une transaction est terminée avant qu'une autre transaction ne commence, Spanner garantit que les clients voient toujours les résultats des transactions dans l'ordre séquentiel. Intuitivement, Spanner est semblable à une base de données utilisant une seule machine.
En contrepartie, Spanner peut abandonner les transactions si une charge de travail présente un fort conflit de lecture/écriture, où de nombreuses transactions lisent des données qu'une autre transaction est en train de mettre à jour, en raison de la nature fondamentale des transactions sérialisables. Toutefois, il s'agit d'une bonne valeur par défaut pour une base de données opérationnelle. Cela vous aide à éviter les problèmes de timing délicats qui ne surviennent généralement qu'en cas de forte simultanéité. Ces problèmes sont difficiles à reproduire et à résoudre. Par conséquent, l'isolation sérialisable offre la meilleure protection contre les anomalies de données. Si une transaction doit être retentée, la latence peut augmenter en raison des nouvelles tentatives de transaction.
Isolation "Repeatable Read"
Dans Spanner, l'isolation de lecture reproductible est implémentée à l'aide d'une technique communément appelée "isolation d'instantané". L'isolation de lecture reproductible dans Spanner garantit que toutes les opérations de lecture d'une transaction voient un instantané cohérent ou fort de la base de données telle qu'elle existait au début de la transaction. Il garantit également que les écritures simultanées sur les mêmes données ne réussissent que s'il n'y a pas de conflits. Cette approche est utile dans les scénarios de conflits de lecture/écriture élevés, où de nombreuses transactions lisent des données que d'autres transactions peuvent modifier. En utilisant un instantané fixe, la lecture reproductible évite les impacts sur les performances du niveau d'isolation sérialisable plus restrictif. Les lectures peuvent s'exécuter sans acquérir de verrous ni bloquer les écritures simultanées, ce qui réduit le nombre de transactions abandonnées qui peuvent nécessiter une nouvelle tentative en raison de conflits de sérialisation potentiels. Dans les cas d'utilisation où vos clients exécutent déjà tout dans une transaction en lecture/écriture et où il est difficile de repenser et d'utiliser des transactions en lecture seule, vous pouvez utiliser l'isolation de lecture reproductible pour améliorer la latence de vos charges de travail.
Contrairement à l'isolation sérialisable, la lecture reproductible peut entraîner des anomalies de données si votre application repose sur des relations ou des contraintes de données spécifiques qui ne sont pas appliquées par le schéma de base de données, en particulier lorsque l'ordre des opérations est important. Dans ce cas, une transaction peut lire des données, prendre des décisions en fonction de ces données, puis écrire des modifications qui enfreignent ces contraintes spécifiques à l'application, même si les contraintes du schéma de base de données sont toujours respectées. Cela se produit, car l'isolation de lecture reproductible permet aux transactions simultanées de se dérouler sans sérialisation stricte. Une anomalie potentielle est appelée biais d'écriture. Elle résulte d'un type particulier de mise à jour simultanée, où chaque mise à jour est acceptée indépendamment, mais où leur effet combiné viole l'intégrité des données de l'application. Par exemple, imaginez un système hospitalier où au moins un médecin doit être de garde à tout moment, et où les médecins peuvent demander à ne pas être de garde pour un service. Avec l'isolation de lecture reproductible, si le Dr Richards et le Dr Smith sont tous les deux de garde pour le même service et tentent simultanément de demander à ne plus être de garde, chaque demande est traitée en parallèle. En effet, les deux transactions indiquent qu'au moins un autre médecin est prévu pour être de garde au début de la transaction, ce qui entraîne une anomalie des données si les transactions réussissent. En revanche, l'utilisation de l'isolation sérialisable empêche ces transactions de violer la contrainte, car les transactions sérialisables détectent les anomalies de données potentielles et abandonnent la transaction. Cela garantit la cohérence des applications en acceptant des taux d'abandon plus élevés.
Dans l'exemple précédent, vous pouvez utiliser la clause SELECT FOR UPDATE
dans l'isolation de lecture reproductible.
La clause SELECT…FOR UPDATE
vérifie si les données lues à l'instantané choisi restent inchangées au moment de la validation. De même, les instructions LMD et les mutations qui lisent les données en interne pour garantir l'intégrité des écritures vérifient également que les données restent inchangées au moment du commit.
Pour en savoir plus, consultez Utiliser l'isolation de lecture reproductible.
Exemple d'utilisation
L'exemple suivant illustre l'avantage d'utiliser l'isolation de lecture reproductible pour éliminer les frais généraux de verrouillage. Transaction 1
et Transaction 2
s'exécutent avec un niveau d'isolation de lecture répétable.
Transaction 1
établit un code temporel d'instantané lorsque l'instruction SELECT
s'exécute.
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 |
*------------+------------------*/
Ensuite, Transaction 2
établit un code temporel d'instantané après le début de Transaction 1
, mais avant son commit. Étant donné que Transaction 1
n'a pas mis à jour les données, la requête SELECT
dans Transaction 2
lit les mêmes données 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
se poursuit après l'engagement de Transaction 2
.
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 |
*------------*/
La valeur UsedBudget
renvoyée par Spanner correspond à la somme du budget lu par Transaction 1
. Cette somme ne reflète que les données présentes dans l'instantané T1
. Il n'inclut pas le budget ajouté par Transaction 2
, car Transaction 2
s'est engagé après l'instantané établi par Transaction 1
T1
. L'utilisation de la lecture reproductible signifie que Transaction 1
n'a pas eu à être abandonnée, même si Transaction 2
a modifié les données lues par Transaction 1
. Toutefois, le résultat renvoyé par Spanner peut ou non être celui attendu.
Conflits de lecture-écriture et exactitude
Dans l'exemple précédent, si les données interrogées par les instructions SELECT
dans Transaction 1
ont été utilisées pour prendre des décisions budgétaires marketing ultérieures, des problèmes d'exactitude peuvent survenir.
Par exemple, supposons qu'il existe un budget total de 400,000
. En nous basant sur le résultat de l'instruction SELECT
dans Transaction 1
, nous pourrions penser qu'il reste 100,000
dans le budget et décider de l'allouer entièrement à 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
est validé, même si Transaction 2
a déjà alloué 50,000
du budget 100,000
restant à un nouvel album AlbumId = 5
.
Vous pouvez utiliser la syntaxe SELECT...FOR UPDATE
pour valider que certaines lectures d'une transaction restent inchangées pendant toute la durée de la transaction afin de garantir son exactitude. Dans l'exemple suivant utilisant SELECT...FOR UPDATE
, Transaction 1
s'arrête au moment du commit.
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;
Pour en savoir plus, consultez Utiliser SELECT FOR UPDATE dans l'isolation de lecture reproductible.
Conflits d'écriture et exactitude
En utilisant le niveau d'isolation "Lecture reproductible", les écritures simultanées sur les mêmes données ne réussissent que s'il n'y a pas de conflits.
Dans l'exemple suivant, Transaction 1
établit un code temporel d'instantané au niveau de la première instruction 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;
Le Transaction 2
suivant lit les mêmes données que Transaction 1
et insère un nouvel élément. Transaction 2
s'engage correctement sans attendre ni abandonner.
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
se poursuit après l'engagement de Transaction 2
.
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
s'arrête, car Transaction 2
a déjà inséré une ligne dans AlbumId = 5
.
Étapes suivantes
Découvrez comment utiliser le niveau d'isolation "Lecture répétable".
Découvrez comment utiliser SELECT FOR UPDATE dans l'isolation de lecture répétable.
Découvrez comment utiliser SELECT FOR UPDATE dans l'isolation sérialisable.
Pour en savoir plus sur la sérialisabilité et la cohérence externe de Spanner, consultez TrueTime et cohérence externe.