SQL Server fournit deux implémentations physiques de la lecture validée niveau d'isolement défini par la norme SQL, verrouillant l'isolement de l'instantané en lecture validée et en lecture validée (RCSI ). Alors que les deux implémentations répondent aux exigences définies dans la norme SQL pour les comportements d'isolement validés en lecture, RCSI a des comportements physiques assez différents de l'implémentation de verrouillage que nous avons examinée dans le précédent article de cette série.
Garanties logiques
La norme SQL exige qu'une transaction fonctionnant au niveau d'isolement de lecture validée ne subisse aucune lecture incorrecte. Une autre façon d'exprimer cette exigence est de dire qu'une transaction validée en lecture ne doit rencontrer que des données validées .
La norme indique également que la lecture des transactions validées pourrait faire l'expérience des phénomènes de simultanéité connus sous le nom de lectures non répétables et de fantômes (bien qu'ils ne soient pas réellement tenus de le faire). En l'occurrence, les deux implémentations physiques de l'isolation validée en lecture dans SQL Server peuvent rencontrer des lectures non répétables et des lignes fantômes, bien que les détails précis soient assez différents.
Une vue ponctuelle des données validées
Si l'option de base de données READ_COMMITTED_SNAPSHOT
en ON
, SQL Server utilise une implémentation de gestion des versions de ligne du niveau d'isolement de lecture validée. Lorsque cette option est activée, les transactions demandant l'isolement de lecture validée utilisent automatiquement l'implémentation RCSI ; aucune modification du code T-SQL existant n'est requise pour utiliser RCSI. Notez bien cependant que ce n'est pas la même chose comme disant que le code se comportera de la même manière sous RCSI comme lors de l'utilisation de l'implémentation de verrouillage de read commited, en fait ce n'est généralement pas le cas .
Il n'y a rien dans la norme SQL qui exige que les données lues par une transaction de lecture validée soient les plus récentes données engagées. L'implémentation SQL Server RCSI en profite pour fournir aux transactions une vue ponctuelle de données validées, où ce point dans le temps est le moment où l'instruction actuelle a commencé exécution (pas le moment où une transaction contenante a commencé).
Ceci est assez différent du comportement de l'implémentation de verrouillage SQL Server de la lecture validée, où l'instruction voit les données les plus récemment validées au moment où chaque élément est physiquement lu . Le verrouillage de la lecture validée libère les verrous partagés aussi rapidement que possible, de sorte que l'ensemble de données rencontrées peut provenir de moments très différents.
Pour résumer, le verrouillage de la lecture validée voit chaque ligne tel qu'il était à l'époque, il a été brièvement verrouillé et lu physiquement; RCSI voit toutes les lignes tels qu'ils étaient au moment où la déclaration a commencé. Les deux implémentations sont garanties de ne jamais voir de données non validées, mais les données qu'elles rencontrent peuvent être très différentes.
Les implications d'une vue ponctuelle
Voir une vue ponctuelle des données validées peut sembler évidemment supérieur au comportement plus complexe de l'implémentation du verrouillage. Il est clair, par exemple, qu'une vue ponctuelle ne peut pas souffrir des problèmes de lignes manquantes ou rencontrer la même ligne plusieurs fois , qui sont tous deux possibles sous verrouillage de l'isolation validée en lecture.
Un deuxième avantage important de RCSI est qu'il n'acquiert pas de verrous partagés lors de la lecture des données, car les données proviennent du magasin de versions de ligne plutôt que d'être accessibles directement. L'absence de verrous partagés peut considérablement améliorer la simultanéité en éliminant les conflits avec des transactions simultanées cherchant à acquérir des verrous incompatibles. Cet avantage est généralement résumé en disant que les lecteurs ne bloquent pas les écrivains sous RCSI, et vice-versa. Comme conséquence supplémentaire de la réduction des blocages dus à des demandes de verrouillage incompatibles, la possibilité de blocages est généralement considérablement réduit lors de l'exécution sous RCSI.
Cependant, ces avantages ne vont pas sans coûts et mises en garde . D'une part, la maintenance des versions des lignes validées consomme des ressources système, il est donc important que l'environnement physique soit configuré pour y faire face, principalement en termes de tempdb exigences en termes de performances et d'espace mémoire/disque.
La deuxième mise en garde est un peu plus subtile :RCSI fournit une vue instantanée des données validées telles qu'elles étaient au début de l'instruction, mais rien n'empêche que les données réelles soient modifiées (et que ces modifications soient validées) pendant l'exécution de l'instruction RCSI. Il n'y a pas de serrures partagées, rappelez-vous. Une conséquence immédiate de ce deuxième point est que le code T-SQL exécuté sous RCSI pourrait prendre des décisions basées sur des informations obsolètes , par rapport à l'état validé actuel de la base de données. Nous en reparlerons bientôt.
Il y a une dernière observation (spécifique à l'implémentation) que je veux faire à propos de RCSI avant de continuer. Fonctions scalaires et multi-instructions exécuter en utilisant un contexte T-SQL interne différent de celui de l'instruction conteneur. Cela signifie que la vue ponctuelle vue à l'intérieur d'un appel de fonction scalaire ou multi-instructions peut être postérieure à la vue ponctuelle vue par le reste de l'instruction. Cela peut entraîner des incohérences inattendues, car différentes parties d'un même relevé voient des données à différents moments dans le temps . Ce comportement étrange et déroutant ne pas s'appliquent aux fonctions en ligne, qui voient le même instantané que l'instruction dans laquelle elles apparaissent.
Lectures non répétables et fantômes
Étant donné une vue ponctuelle au niveau de l'instruction de l'état validé de la base de données, il se peut qu'il ne soit pas immédiatement évident qu'une transaction de lecture validée sous RCSI puisse rencontrer le phénomène de lecture non répétable ou de ligne fantôme. En effet, si nous limitons notre réflexion à la portée d'une seule déclaration , aucun de ces phénomènes n'est possible sous RCSI.
Lire plusieurs fois les mêmes données dans la même instruction sous RCSI renverra toujours les mêmes valeurs de données, aucune donnée ne disparaîtra entre ces lectures et aucune nouvelle donnée n'apparaîtra non plus. Si vous vous demandez quel type d'instruction peut lire les mêmes données plus d'une fois, pensez aux requêtes qui font référence à la même table plus d'une fois, peut-être dans une sous-requête.
La cohérence de lecture au niveau de l'instruction est une conséquence évidente des lectures émises par rapport à un instantané fixe des données. La raison pour laquelle RCSI ne le fait pas fournir une protection contre les lectures non répétables et les fantômes est que ces phénomènes standard SQL sont définis au niveau de la transaction. Plusieurs déclarations au sein d'une transaction exécutée au RCSI peuvent voir des données différentes, car chaque déclaration voit une vue ponctuelle à partir du moment cette déclaration particulière commencé.
Pour résumer, chaque instruction dans une transaction RCSI voit un ensemble de données statiques validées, mais cet ensemble peut changer entre les instructions à l'intérieur de la même transaction.
Données obsolètes
La possibilité que notre code T-SQL prenne une décision importante sur la base d'informations obsolètes est plus qu'un peu troublante. Considérez un instant que l'instantané à un moment donné utilisé par une seule instruction exécutée sous RCSI peut être arbitrairement ancien .
Une instruction qui s'exécute pendant une période de temps considérable continuera à voir l'état validé de la base de données tel qu'il était au début de l'instruction. Pendant ce temps, il manque à l'instruction toutes les modifications validées qui se sont produites dans la base de données depuis lors.
Cela ne veut pas dire que les problèmes associés à l'accès aux données obsolètes sous RCSI se limitent à de longue durée déclarations, mais les problèmes pourraient certainement être plus prononcés dans de tels cas.
Une question de timing
Ce problème de données obsolètes s'applique en principe à toutes les déclarations RCSI, quelle que soit la rapidité avec laquelle elles peuvent être complétées. Quelle que soit la petite fenêtre temporelle, il y a toujours une chance qu'une opération simultanée modifie l'ensemble de données avec lequel nous travaillons, sans que nous soyons conscients de ce changement. Examinons à nouveau l'un des exemples simples que nous avons utilisés précédemment lors de l'exploration du comportement du verrouillage de la lecture validée :
INSERT dbo.OverdueInvoices SELECT I.InvoiceNumber FROM dbo.Invoices AS I WHERE I.TotalDue > ( SELECT SUM(P.Amount) FROM dbo.Payments AS P WHERE P.InvoiceNumber = I.InvoiceNumber );
Lorsqu'elle est exécutée sous RCSI, cette instruction ne peut pas voir toutes les modifications de base de données validées qui se produisent après le début de l'exécution de l'instruction. Bien que nous ne rencontrions pas les problèmes de lignes manquées ou rencontrées plusieurs fois possibles avec l'implémentation du verrouillage, une transaction simultanée peut ajouter un paiement qui devrait pour éviter qu'un client ne reçoive une lettre d'avertissement sévère concernant un paiement en retard après le début de l'exécution de la déclaration ci-dessus.
Vous pouvez probablement penser à de nombreux autres problèmes potentiels qui pourraient se produire dans ce scénario, ou dans d'autres qui sont conceptuellement similaires. Plus l'instruction s'exécute longtemps, plus sa vue de la base de données devient obsolète et plus la portée de conséquences éventuellement imprévues est grande.
Bien sûr, il existe de nombreux facteurs atténuants dans cet exemple spécifique. Le comportement pourrait bien être considéré comme parfaitement acceptable. Après tout, envoyer une lettre de rappel parce qu'un paiement est arrivé quelques secondes trop tard est une action facilement défendable. Le principe demeure cependant.
Échecs des règles métier et risques d'intégrité
L'utilisation d'informations obsolètes peut entraîner des problèmes plus graves que l'envoi d'une lettre d'avertissement quelques secondes plus tôt. Un bon exemple de cette classe de faiblesse peut être vu avec le code déclencheur utilisé pour appliquer une règle d'intégrité qui est peut-être trop complexe à appliquer avec des contraintes d'intégrité référentielles déclaratives. Pour illustrer, considérez le code suivant, qui utilise un déclencheur pour appliquer une variation d'une contrainte de clé étrangère, mais qui applique la relation uniquement pour certaines lignes de la table enfant :
ALTER DATABASE Sandpit SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE; GO SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO CREATE TABLE dbo.Parent (ParentID integer PRIMARY KEY); GO CREATE TABLE dbo.Child ( ChildID integer IDENTITY PRIMARY KEY, ParentID integer NOT NULL, CheckMe bit NOT NULL ); GO CREATE TRIGGER dbo.Child_AI ON dbo.Child AFTER INSERT AS BEGIN -- Child rows with CheckMe = true -- must have an associated parent row IF EXISTS ( SELECT ins.ParentID FROM inserted AS ins WHERE ins.CheckMe = 1 EXCEPT SELECT P.ParentID FROM dbo.Parent AS P ) BEGIN RAISERROR ('Integrity violation!', 16, 1); ROLLBACK TRANSACTION; END END; GO -- Insert parent row #1 INSERT dbo.Parent (ParentID) VALUES (1);
Considérons maintenant une transaction en cours d'exécution dans une autre session (utilisez une autre fenêtre SSMS pour cela si vous suivez) qui supprime la ligne parent n° 1, mais ne s'engage pas encore :
SET TRANSACTION ISOLATION LEVEL READ COMMITTED; BEGIN TRANSACTION; DELETE FROM dbo.Parent WHERE ParentID = 1;
De retour dans notre session d'origine, nous essayons d'insérer une ligne enfant (cochée) qui fait référence à ce parent :
INSERT dbo.Child (ParentID, CheckMe) VALUES (1, 1);
Le code du déclencheur s'exécute, mais parce que RCSI ne voit que committed données au moment où l'instruction a commencé, elle voit toujours la ligne parente (pas la suppression non validée) et l'insertion réussit !
La transaction qui a supprimé la ligne parente peut maintenant valider sa modification avec succès, laissant la base de données dans un état incohérent état en termes de logique de déclenchement :
COMMIT TRANSACTION; SELECT P.* FROM dbo.Parent AS P; SELECT C.* FROM dbo.Child AS C;
Il s'agit bien sûr d'un exemple simplifié, et qui pourrait facilement être contourné en utilisant les fonctions de contrainte intégrées. Des règles métier beaucoup plus complexes et des contraintes de pseudo-intégrité peuvent être écrites à l'intérieur et en dehors des déclencheurs . Le potentiel de comportement incorrect sous RCSI devrait être évident.
Comportement de blocage et dernières données validées
J'ai mentionné plus tôt qu'il n'est pas garanti que le code T-SQL se comporte de la même manière sous RCSI read commited qu'avec l'implémentation de verrouillage. L'exemple de code de déclencheur précédent en est une bonne illustration, mais je dois souligner que le problème général ne se limite pas aux déclencheurs .
RCSI n'est généralement pas un bon choix pour tout code T-SQL dont l'exactitude dépend du blocage s'il existe une modification simultanée non validée. RCSI peut également ne pas être le bon choix si le code dépend de la lecture current données validées, plutôt que les dernières données validées au moment où l'instruction a commencé. Ces deux considérations sont liées, mais elles ne sont pas la même chose.
Verrouiller la lecture validée sous RCSI
SQL Server fournit un moyen de demander le verrouillage lecture validée lorsque RCSI est activé, en utilisant l'indicateur de table READCOMMITTEDLOCK
. Nous pouvons modifier notre déclencheur pour éviter les problèmes indiqués ci-dessus en ajoutant cet indice au tableau qui nécessite un comportement de blocage pour fonctionner correctement :
ALTER TRIGGER dbo.Child_AI ON dbo.Child AFTER INSERT AS BEGIN -- Child rows with CheckMe = true -- must have an associated parent row IF EXISTS ( SELECT ins.ParentID FROM inserted AS ins WHERE ins.CheckMe = 1 EXCEPT SELECT P.ParentID FROM dbo.Parent AS P WITH (READCOMMITTEDLOCK) -- NEW!! ) BEGIN RAISERROR ('Integrity violation!', 16, 1); ROLLBACK TRANSACTION; END END;
Avec cette modification en place, la tentative d'insertion des blocs de lignes enfants potentiellement orphelins jusqu'à ce que la transaction de suppression soit validée (ou abandonnée). Si la suppression est validée, le code du déclencheur détecte la violation d'intégrité et génère l'erreur attendue.
Identifier les requêtes qui pourraient ne pas s'exécuter correctement sous RCSI est une tâche non triviale qui peut nécessiter des tests approfondis pour bien faire (et rappelez-vous que ces problèmes sont assez généraux et ne se limitent pas au code de déclenchement !) Ajoutez également le READCOMMITTEDLOCK
un indice à chaque table qui en a besoin peut être un processus fastidieux et sujet aux erreurs. Jusqu'à ce que SQL Server fournisse une option plus large pour demander l'implémentation du verrouillage si nécessaire, nous sommes obligés d'utiliser les conseils de table.
La prochaine fois
Le prochain article de cette série poursuit notre examen de l'isolement d'instantané validé en lecture, avec un regard sur le comportement surprenant des instructions de modification de données sous RCSI.
[ Voir l'index pour toute la série ]