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 de lecture répétable
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 tel qu'il 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 conflit. 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.
Avec sa concurrence optimiste par défaut, les lectures s'exécutent sans acquérir de verrous et sans 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. Avec la concurrence pessimiste, les opérations de lecture utilisent des instantanés, mais des verrous exclusifs s'appliquent aux données lues à partir des requêtes FOR UPDATE ou des indications lock_scanned_ranges=exclusive, ainsi qu'aux données écrites avec des requêtes LMD.
Pour les charges de travail qui migrent depuis d'autres bases de données, nous vous recommandons de configurer votre application pour qu'elle utilise l'isolation de lecture reproductible dans Spanner. La sémantique des transactions de lecture reproductible, en particulier le verrouillage pour les lectures, correspond aux niveaux d'isolation par défaut de la plupart des autres bases de données (par exemple, MySQL et PostgreSQL). Cela permet de réduire la nécessité de repenser votre application pour qu'elle fonctionne avec le niveau d'isolation sérialisable par défaut de Spanner.
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 au niveau du snapshot 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 de la validation. De plus, avec la simultanéité pessimiste, les données lues par SELECT ... FOR UPDATE et les données écrites par les instructions LMD acquièrent des verrous exclusifs pour empêcher toute transaction future de valider des modifications conflictuelles avant la validation de la transaction en cours.
Pour les charges de travail qui migrent depuis d'autres bases de données utilisant des requêtes FOR UPDATE, nous vous recommandons de configurer votre application pour qu'elle utilise l'isolation de lecture reproductible avec la concurrence pessimiste dans Spanner. L'application continue d'acquérir des verrous pour les données lues par SELECT ... FOR UPDATE, ce qui est le comportement par défaut dans d'autres bases de données.
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 répétable 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 s'engage avec succès, 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.
Vous pouvez également utiliser la concurrence pessimiste, qui acquiert des verrous exclusifs sur les données lues par l'instruction SELECT...FOR UPDATE. Par exemple, Transaction 1 s'arrête au moment de l'opération commit, car Transaction 2 a validé ses modifications avant que Transaction 1 n'acquière les verrous, ce qui entraîne un conflit. Toutefois, si l'ordre des transactions amène Transaction 2 à tenter de mettre à jour le budget marketing après que Transaction 1 a acquis des verrous, Transaction 2 attend que Transaction 1 valide et libère les verrous avant de pouvoir continuer. L'option de concurrence pessimiste sérialise l'accès aux données.
Pour en savoir plus, consultez la section Contrôle de la simultanéité.
Conflits d'écriture et exactitude
En utilisant le niveau d'isolation de 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 avec succès 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 annule l'opération, 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".
En savoir plus sur le contrôle de la simultanéité
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.