Database
 sql >> Base de données >  >> RDS >> Database

Le niveau d'isolement de lecture non validée

[ Voir l'index pour toute la série ]

La lecture non validée est le plus faible des quatre niveaux d'isolation de transaction définis dans le standard SQL (et des six implémentés dans SQL Server). Il permet les trois soi-disant "phénomènes de concurrence", lectures sales , lectures non répétables , et fantômes :

La plupart des gens de la base de données sont conscients de ces phénomènes, au moins dans les grandes lignes, mais tout le monde ne se rend pas compte qu'ils ne décrivent pas complètement les garanties d'isolement offertes ; ils ne décrivent pas non plus intuitivement les différents comportements auxquels on peut s'attendre dans une implémentation spécifique telle que SQL Server. Plus d'informations à ce sujet plus tard.

Isolation des transactions – le « I » dans ACID

Chaque commande SQL s'exécute dans une transaction (explicite, implicite ou auto-commit). Chaque transaction est associée à un niveau d'isolement, qui détermine son degré d'isolement par rapport aux effets d'autres transactions simultanées. Ce concept quelque peu technique a des implications importantes sur la façon dont les requêtes s'exécutent et la qualité des résultats qu'elles produisent.

Considérez une requête simple qui compte toutes les lignes d'une table. Si cette requête pouvait être exécutée instantanément (ou sans modifications de données simultanées), il ne pourrait y avoir qu'une seule réponse correcte :le nombre de lignes physiquement présentes dans la table à ce moment précis. En réalité, l'exécution de la requête prendra un certain temps et le résultat dépendra du nombre de lignes réellement rencontrées par le moteur d'exécution lorsqu'il traverse la structure physique choisie pour accéder aux données.

Si des lignes sont ajoutées (ou supprimées) à la table par des transactions simultanées alors que l'opération de comptage est en cours, des résultats différents peuvent être obtenus selon que la transaction de comptage de lignes rencontre toutes, certaines ou aucune de ces modifications simultanées - ce qui dépend à son tour du niveau d'isolement de la transaction de comptage de lignes.

En fonction du niveau d'isolement, des détails physiques et du moment des opérations simultanées, notre transaction de comptage pourrait même produire un résultat qui n'a jamais reflété fidèlement l'état validé de la table à tout moment de la transaction.

Exemple

Considérez une transaction de comptage de lignes qui démarre au temps T1 et parcourt la table du début à la fin (dans l'ordre des clés d'index clusterisées, pour les besoins de l'argument). À ce moment, il y a 100 lignes validées dans la table. Quelque temps plus tard (au temps T2), notre transaction de comptage a rencontré 50 de ces lignes. Au même moment, une transaction simultanée insère deux lignes dans la table et s'engage peu de temps après au temps T3 (avant la fin de la transaction de comptage). L'une des lignes insérées se trouve dans la moitié de la structure d'index cluster que notre transaction de comptage a déjà traitée, tandis que l'autre ligne insérée se trouve dans la partie non comptée.

Lorsque la transaction de comptage de lignes est terminée, elle signale 101 lignes dans ce scénario ; 100 lignes initialement dans le tableau plus la seule ligne insérée rencontrée lors de l'analyse. Ce résultat est en contradiction avec l'historique commité de la table :il y avait 100 lignes commitées aux instants T1 et T2, puis 102 lignes commitées à l'instant T3. Il n'y a jamais eu de moment où il y avait 101 lignes validées.

La chose surprenante (peut-être, selon la profondeur à laquelle vous avez réfléchi à ces choses auparavant) est que ce résultat est possible au niveau d'isolement de lecture validée par défaut (verrouillage), et même sous un isolement de lecture reproductible. Ces deux niveaux d'isolement sont garantis pour lire uniquement les données validées, mais nous avons obtenu un résultat qui ne représente aucun état validé de la base de données !

Analyse

Le seul niveau d'isolation de transaction qui fournit une isolation complète des effets de concurrence est sérialisable. L'implémentation SQL Server du niveau d'isolement sérialisable signifie qu'une transaction verra les dernières données validées, à partir du moment où les données ont été verrouillées pour la première fois pour l'accès. De plus, l'ensemble de données rencontré sous isolement sérialisable est garanti de ne pas changer d'appartenance avant la fin de la transaction.

L'exemple de comptage de lignes met en évidence un aspect fondamental de la théorie des bases de données :nous devons être clairs sur ce qu'un résultat "correct" signifie pour une base de données qui subit des modifications simultanées, et nous devons comprendre les compromis que nous faisons lors de la sélection d'un isolement. niveau inférieur à sérialisable.

Si nous avons besoin d'une vue ponctuelle de l'état validé de la base de données, nous devons utiliser l'isolement d'instantané (pour les garanties au niveau de la transaction) ou lire l'isolement d'instantané validé (pour les garanties au niveau de l'instruction). Notez cependant qu'une vue ponctuelle signifie que nous n'opérons pas nécessairement sur l'état validé actuel de la base de données ; en effet, nous pouvons utiliser des informations obsolètes. D'un autre côté, si nous sommes satisfaits des résultats basés uniquement sur les données validées (bien qu'éventuellement à des moments différents), nous pouvons choisir de nous en tenir au niveau d'isolement de lecture validée par défaut.

Pour être sûr de produire des résultats (et de prendre des décisions !) Basés sur le dernier ensemble de données validées, pour un historique en série des opérations sur la base de données, nous aurions besoin d'une isolation des transactions sérialisables. Bien sûr, cette option est généralement la plus coûteuse en termes d'utilisation des ressources et de réduction de la simultanéité (y compris un risque accru de blocages).

Dans l'exemple de comptage de lignes, les deux niveaux d'isolement d'instantané (SI et RCSI) donneraient un résultat de 100 lignes, représentant le nombre de lignes validées au début de l'instruction (et de la transaction dans ce cas). L'exécution de la requête lors du verrouillage de la lecture validée ou de l'isolement de lecture répétable peut produire un résultat de 100, 101 ou 102 lignes, en fonction de la synchronisation, de la granularité du verrouillage, de la position d'insertion de la ligne et de la méthode d'accès physique choisie. Sous isolation sérialisable, le résultat serait de 100 ou 102 lignes, selon laquelle des deux transactions simultanées est considérée comme s'étant exécutée en premier.

Quelle est la gravité de la lecture non validée ?

Après avoir introduit l'isolement de lecture non validée comme le plus faible des niveaux d'isolement disponibles, vous devriez vous attendre à ce qu'il offre des garanties d'isolement encore plus faibles que le verrouillage de lecture validée (le niveau d'isolement suivant le plus élevé). En effet, c'est le cas; mais la question est:à quel point est-ce pire que le verrouillage de l'isolement engagé en lecture?

Afin que nous commencions avec le bon contexte, voici une liste des principaux effets de concurrence qui peuvent être rencontrés sous le niveau d'isolement de lecture validée par verrouillage de SQL Server :

  • Lignes validées manquantes
  • Lignes rencontrées plusieurs fois
  • Différentes versions de la même ligne rencontrées dans une seule instruction/plan de requête
  • Données de colonne validées à différents moments dans le temps dans la même ligne (exemple)

Ces effets de concurrence sont tous dus à l'implémentation du verrouillage de la lecture validée qui ne prend que des verrous partagés à très court terme lors de la lecture des données. Le niveau d'isolement de lecture non validée va encore plus loin, en ne prenant pas du tout de verrous partagés, ce qui entraîne la possibilité supplémentaire de "lectures erronées".

Lectures erronées

Pour rappel, une « lecture modifiée » fait référence à la lecture de données qui sont modifiées par une autre transaction simultanée (où la « modification » comprend les opérations d'insertion, de mise à jour, de suppression et de fusion). En d'autres termes, une lecture incorrecte se produit lorsqu'une transaction lit des données qu'une autre transaction a modifiées, avant que la transaction modificatrice n'ait validé ou abandonné ces modifications.

Avantages et inconvénients

Les principaux avantages de l'isolement en lecture non validée sont le potentiel réduit de blocage et d'interblocage dû à des verrous incompatibles (y compris le blocage inutile dû à l'escalade de verrous) et éventuellement des performances accrues (en évitant d'avoir à acquérir et à libérer des verrous partagés).

L'inconvénient potentiel le plus évident de l'isolement de lecture non validée est (comme son nom l'indique) que nous pouvons lire des données non validées (même des données qui ne sont jamais commis, dans le cas d'une annulation de transaction). Dans une base de données où les restaurations sont relativement rares, la question de la lecture des données non validées peut être considérée comme un simple problème de timing, puisque les données en question seront sûrement validées à un moment donné, et probablement assez tôt. Nous avons déjà vu des incohérences liées à la synchronisation dans l'exemple de comptage de lignes (qui fonctionnait à un niveau d'isolement plus élevé). On pourrait donc se demander à quel point il est préoccupant de lire les données "trop ​​tôt".

Il est clair que la réponse dépend des priorités et du contexte locaux, mais une décision éclairée d'utiliser l'isolement de lecture non validée semble certainement possible. Il y a plus à penser cependant. L'implémentation SQL Server du niveau d'isolement en lecture non validée inclut certains comportements subtils dont nous devons être conscients avant de faire ce "choix éclairé".

Scans d'ordre d'allocation

L'utilisation de l'isolation en lecture non validée est considérée par SQL Server comme un signal indiquant que nous sommes prêts à accepter les incohérences pouvant survenir à la suite d'une analyse ordonnée par allocation.

Normalement, le moteur de stockage ne peut choisir une analyse ordonnée par allocation que si les données sous-jacentes sont garanties de ne pas changer pendant l'analyse (car, par exemple, la base de données est en lecture seule ou un indicateur de verrouillage de table a été spécifié). Cependant, lorsque l'isolation en lecture non validée est utilisée, le moteur de stockage peut toujours choisir une analyse ordonnée par allocation même lorsque les données sous-jacentes peuvent être modifiées par des transactions simultanées.

Dans ces circonstances, l'analyse ordonnée par allocation peut manquer complètement certaines données validées ou rencontrer d'autres données validées plus d'une fois. L'accent est mis sur l'absence ou le double comptage des engagés données (ne lisant pas de données non validées), il ne s'agit donc pas de "lectures sales" en tant que telles. Cette décision de conception (autoriser les analyses ordonnées par allocation sous isolement de lecture non validée) est considérée par certaines personnes comme plutôt controversée.

En guise de mise en garde, je dois préciser que le risque plus général de manquer ou de double compter des lignes validées ne se limite pas à lire l'isolement non validé. Il est certainement possible de voir des effets similaires sous verrouillage de lecture validée et de lecture répétable (comme nous l'avons vu précédemment) mais cela se produit via un mécanisme différent. Lignes validées manquantes ou rencontrées plusieurs fois en raison d'une analyse ordonnée par allocation sur des données changeantes est spécifique à l'utilisation de l'isolement de lecture non validée.

Lecture de données "corrompues"

Des résultats qui semblent défier la logique (et même vérifier les contraintes !) sont possibles sous verrouillage de l'isolation validée en lecture (encore une fois, voir cet article de Craig Freedman pour quelques exemples). Pour résumer, le fait est que le verrouillage de la lecture validée peut voir les données validées à différents moments dans le temps - même pour une seule ligne si, par exemple, le plan de requête utilise des techniques telles que l'intersection d'index.

Ces résultats peuvent être inattendus, mais ils sont tout à fait conformes à la garantie de ne lire que les données validées. Il est tout simplement indéniable que des garanties de cohérence des données plus élevées nécessitent des niveaux d'isolement plus élevés.

Ces exemples peuvent même être assez choquants, si vous ne les avez pas vus auparavant. Bien sûr, les mêmes résultats sont possibles avec un isolement de lecture non validée, mais autoriser les lectures incorrectes ajoute une dimension supplémentaire :les résultats peuvent inclure des données validées et non validées à différents moments dans le temps, même pour la même ligne.

En allant plus loin, il est même possible qu'une transaction lue non validée lise une valeur de colonne unique dans un état mixte de données validées et non validées. Cela peut se produire lors de la lecture d'une valeur LOB (par exemple, xml ou l'un des types 'max') si la valeur est stockée sur plusieurs pages de données. Une lecture non validée peut rencontrer des données validées ou non validées à différents moments dans le temps sur différentes pages, ce qui donne une valeur finale à une seule colonne qui est un mélange de valeurs !

Pour prendre un exemple, considérons une seule colonne varchar(max) qui contient initialement 10 000 caractères 'x'. Une transaction simultanée met à jour cette valeur à 10 000 caractères 'y'. Une transaction de lecture non validée peut lire les caractères 'x' d'une page du LOB et les caractères 'y' d'une autre, ce qui donne une valeur de lecture finale contenant un mélange de caractères 'x' et 'y'. Il est difficile d'affirmer que cela ne représente pas la lecture de données "corrompues".

Démo

Créez une table en cluster avec une seule ligne de données LOB :

CREATE TABLE dbo.Test
(
    RowID integer PRIMARY KEY,
    LOB varchar(max) NOT NULL,
);
 
INSERT dbo.Test
    (RowID, LOB)
VALUES
    (1, REPLICATE(CONVERT(varchar(max), 'X'), 16100));

Dans une session distincte, exécutez le script suivant pour lire la valeur LOB lors de l'isolement en lecture non validée :

-- Run this in session 2
SET NOCOUNT ON;
 
DECLARE 
    @ValueRead varchar(max) = '',
    @AllXs varchar(max) = REPLICATE(CONVERT(varchar(max), 'X'), 16100),
    @AllYs varchar(max) = REPLICATE(CONVERT(varchar(max), 'Y'), 16100);
 
WHILE 1 = 1
BEGIN
    SELECT @ValueRead = T.LOB
    FROM dbo.Test AS T WITH (READUNCOMMITTED)
    WHERE T.RowID = 1;
 
    IF @ValueRead NOT IN (@AllXs, @AllYs)
    BEGIN
    	PRINT LEFT(@ValueRead, 8000);
        PRINT RIGHT(@ValueRead, 8000);
        BREAK;
    END
END;

Dans la première session, exécutez ce script pour écrire des valeurs alternatives dans la colonne LOB :

-- Run this in session 1
SET NOCOUNT ON;
 
DECLARE 
    @AllXs varchar(max) = REPLICATE(CONVERT(varchar(max), 'X'), 16100),
    @AllYs varchar(max) = REPLICATE(CONVERT(varchar(max), 'Y'), 16100);
 
WHILE 1 = 1
BEGIN
    UPDATE dbo.Test
    SET LOB = @AllYs
    WHERE RowID = 1;
 
    UPDATE dbo.Test
    SET LOB = @AllXs
    WHERE RowID = 1;
END;

Après un court instant, le script de la deuxième session se terminera, après avoir lu un état mixte pour la valeur LOB, par exemple :

Ce problème particulier est limité aux lectures de valeurs de colonne LOB qui sont réparties sur plusieurs pages, non pas à cause des garanties fournies par le niveau d'isolement, mais parce que SQL Server utilise des verrous au niveau de la page pour garantir l'intégrité physique. Un effet secondaire de ce détail d'implémentation est qu'il empêche de telles lectures de données "corrompues" si les données d'une seule opération de lecture résident sur une seule page.

Selon la version de SQL Server dont vous disposez, si des données "d'état mixte" sont lues pour une colonne xml, vous obtiendrez soit une erreur résultant du résultat xml éventuellement mal formé, aucune erreur du tout, soit l'erreur spécifique non validée 601 , "Impossible de poursuivre l'analyse avec NOLOCK en raison du déplacement des données." La lecture de données à états mixtes pour d'autres types de LOB n'entraîne généralement pas de message d'erreur ; l'application ou la requête consommatrice n'a aucun moyen de savoir qu'elle vient de subir le pire type de lecture sale. Pour compléter l'analyse, une ligne à états mixtes non LOB lue à la suite d'une intersection d'index n'est jamais signalée comme une erreur.

Le message ici est que si vous utilisez l'isolement de lecture non validée, vous acceptez que les lectures modifiées incluent la possibilité de lire des valeurs LOB à états mixtes "corrompues".

L'indice NOLOCK

Je suppose qu'aucune discussion sur le niveau d'isolement de lecture non validée ne serait complète sans au moins mentionner cet indice de table (largement surutilisé et mal compris). L'indicateur lui-même n'est qu'un synonyme de l'indicateur de table READUNCOMMITTED. Il remplit exactement la même fonction :l'objet auquel il est appliqué est accessible à l'aide d'une sémantique d'isolement en lecture non validée (bien qu'il y ait une exception).

En ce qui concerne le nom "NOLOCK", cela signifie simplement qu'aucun verrou partagé n'est pris lors de la lecture des données . Les autres verrous (stabilité du schéma, verrous exclusifs pour la modification des données, etc.) sont toujours pris comme d'habitude.

D'une manière générale, les indicateurs NOLOCK doivent être à peu près aussi courants que les autres indicateurs de table de niveau d'isolation par objet, tels que SERIALIZABLE et READCOMMITTEDLOCK. C'est-à-dire :pas très courant du tout, et utilisé uniquement lorsqu'il n'y a pas de bonne alternative, un objectif bien défini et un complet compréhension des conséquences.

Un exemple d'utilisation légitime de NOLOCK (ou READUNCOMMITTED) est lors de l'accès aux DMV ou à d'autres vues système, où un niveau d'isolement plus élevé peut provoquer des conflits indésirables sur les structures de données non utilisateur. Un autre exemple de cas limite pourrait être celui où une requête doit accéder à une partie importante d'une grande table, qui est garantie de ne jamais subir de modifications de données pendant l'exécution de la requête suggérée. Il devrait y avoir une bonne raison de ne pas utiliser l'isolement d'instantané ou de lecture d'instantané validé à la place, et les augmentations de performances attendues devraient être testées, validées et comparées, par exemple, à l'utilisation d'un seul indice de verrouillage de table partagé.

L'utilisation la moins souhaitable de NOLOCK est celle qui est malheureusement la plus courante :l'appliquer à chaque objet d'une requête comme une sorte d'interrupteur magique plus rapide. Avec la meilleure volonté du monde, il n'y a tout simplement pas de meilleur moyen de rendre le code SQL Server résolument amateur. Si vous avez légitimement besoin d'une isolation en lecture non validée pour une requête, un bloc de code ou un module, il est probablement préférable de définir le niveau d'isolation de session de manière appropriée et de fournir des commentaires pour justifier l'action.

Réflexions finales

La lecture non validée est un choix légitime pour le niveau d'isolation des transactions, mais cela doit être un choix éclairé. Pour rappel, voici quelques-uns des phénomènes de concurrence possibles dans le cadre de l'isolation validée en lecture verrouillée par défaut de SQL Server :

  • Lignes précédemment validées manquantes
  • Lignes validées rencontrées plusieurs fois
  • Différentes versions validées de la même ligne rencontrées dans une seule instruction/plan de requête
  • Données validées à différents moments dans le temps sur la même ligne (mais dans des colonnes différentes)
  • Lectures de données validées qui semblent contredire les contraintes activées et vérifiées

Selon votre point de vue, cela pourrait être une liste assez choquante d'incohérences possibles pour le niveau d'isolement par défaut. À cette liste, lisez les ajouts d'isolation non validés :

  • Lectures erronées (rencontrant des données qui n'ont pas encore été validées et qui ne le seront peut-être jamais)
  • Lignes contenant un mélange de données validées et non validées
  • Lignes manquantes/dupliquées en raison d'analyses ordonnées par allocation
  • Valeurs LOB individuelles ("corrompues") à l'état mixte (colonne unique)
  • Erreur 601 - "Impossible de continuer l'analyse avec NOLOCK en raison du déplacement des données" (exemple).

Si vos principales préoccupations transactionnelles concernent les effets secondaires du verrouillage de l'isolation validée en lecture - blocage, surcharge de verrouillage, réduction de la simultanéité en raison de l'escalade des verrous, etc. (RCSI) ou isolation d'instantané (SI). Celles-ci ne sont cependant pas gratuites et les mises à jour sous RCSI en particulier ont des comportements contre-intuitifs.

Pour les scénarios qui exigent les niveaux les plus élevés de garanties de cohérence, sérialisable reste le seul choix sûr. Pour les opérations critiques en termes de performances sur des données en lecture seule (par exemple, les grandes bases de données qui sont effectivement en lecture seule entre les fenêtres ETL), définir explicitement la base de données sur READ_ONLY peut également être un bon choix (les verrous partagés ne sont pas pris lorsque la base de données est en lecture seule, et il n'y a aucun risque d'incohérence).

Il y aura également un nombre relativement restreint d'applications pour lesquelles l'isolement en lecture non validée est le bon choix. Ces applications doivent se contenter de résultats approximatifs et de la possibilité de données parfois incohérentes, apparemment non valides (en termes de contraintes) ou "sans doute corrompues". Si les données changent relativement peu fréquemment, le risque de ces incohérences est également moindre.

[ Voir l'index pour toute la série ]