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

Le correctif de bogue R2 2008 qui brise RCSI

L'un des correctifs inclus dans la mise à jour cumulative 11 pour SQL Server 2008 R2 Service Pack 2 résout un « blocage incorrect » qui peut se produire dans un scénario spécifique (expliqué plus loin dans cet article). Malheureusement, le correctif introduit un nouveau bogue, où les requêtes SELECT sous RCSI (isolement d'instantané validé en lecture) commencent à prendre des verrous partagés d'intention au niveau de la table. Par conséquent, vous pouvez constater une augmentation du blocage (et potentiellement de l'interblocage) pour les requêtes RCSI après l'application de 2008 R2 SP2 CU11 (ou version ultérieure).

Cela sera une mauvaise surprise pour quiconque est habitué à ce que les lecteurs ne bloquent pas les auteurs (et vice-versa) lors de l'utilisation de RCSI. Il n'y a pas de correctif pour le bogue RCSI au moment de la rédaction. En fait, l'élément Connect créé par Eugene Karpovich pour signaler le problème a été fermé comme "Ne sera pas résolu", bien que je comprenne que cette décision est actuellement en cours de révision.

Normalement, ce problème n'est peut-être pas une préoccupation majeure, car les mises à jour cumulatives ne sont généralement pas aussi largement appliquées que les service packs complets. Cependant, Microsoft a récemment annoncé qu'il y aura un Final Service Pack 3 pour SQL Server 2008 R2. Ce service pack sera un simple déploiement des mises à jour cumulatives SP2 existantes (jusqu'à CU13 inclus), mais sans nouveaux correctifs. Le résultat de tout cela est que, à moins que quelque chose ne change entre-temps, les utilisateurs appliquant SP3 commenceront soudainement à être affectés par le bogue RCSI introduit dans CU11.

edit :juste avant la publication de cet article, Microsoft a confirmé que cette régression sera corrigée dans le SP3.

Le même bogue « blocage incorrect » (dont le correctif introduit le nouveau bogue) a également été corrigé dans la mise à jour cumulative 8 pour SQL Server 2012 Service Pack 1, comme décrit dans KB2923460. Le correctif pour SQL Server 2012 est différent et pas introduire le nouveau problème RCSI.

SQL Server 2014 n'a jamais été affecté par l'un ou l'autre problème, pour autant que je sache. Il n'y a certainement aucune documentation pour indiquer le contraire, et les tests que j'ai effectués sur 2014 RTM, CU1 et CU2 ne reproduisent aucun bogue.

Le bogue RCSI R2 2008

Une requête SELECT exécutée sous RCSI prend généralement uniquement un verrou de stabilité de schéma (Sch-S), qui est compatible avec tous les autres verrous à l'exception d'un verrou de modification de schéma (Sch-M). Lorsque CU11 (ou une version ultérieure) est appliquée à une instance SQL Server 2008 R2, ces requêtes commencent à prendre un verrou partagé d'intention au niveau de la table (Tab-IS). Le script de test suivant peut être utilisé pour démontrer la différence de comportement :

USE master;
GO
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET NOCOUNT ON;
GO
CREATE DATABASE RCSI;
GO
ALTER DATABASE RCSI
SET READ_COMMITTED_SNAPSHOT ON;
GO
ALTER DATABASE RCSI
SET ALLOW_SNAPSHOT_ISOLATION OFF;
GO
USE RCSI;
GO
CREATE TABLE dbo.Test
(
    id integer IDENTITY NOT NULL, 
    col1 integer NOT NULL,
 
    CONSTRAINT PK_Test
    PRIMARY KEY CLUSTERED (id)
);
GO
INSERT dbo.Test 
    (col1) 
VALUES 
    (1), (2), (3), (4);
GO
-- Show locks
DBCC TRACEON (1200, 3604, -1) WITH NO_INFOMSGS;
SELECT * FROM dbo.Test;
DBCC TRACEOFF (1200, 3604, -1) WITH NO_INFOMSGS;
GO
ALTER DATABASE RCSI
SET SINGLE_USER
WITH ROLLBACK IMMEDIATE;
 
USE master;
 
DROP DATABASE RCSI;

Lorsqu'il est exécuté sur une instance de SQL Server 2008 R2 sans le bogue, la sortie de débogage affiche un seul verrou Sch-S pris pour l'instruction de test comme prévu :

Processus d'acquisition du verrou Sch-S sur OBJECT :7:2105058535:0 résultat :OK
Processus de libération du verrou sur OBJECT :7:2105058535:0

Lorsqu'il est exécuté sur SQL Server 2008 R2 build 10.50.4302 (ou supérieur), le résultat est similaire à :

Procéder à l'acquisition du verrou IS sur OBJECT :7:2105058535:0 résultat :OK
Procéder à la libération du verrou sur OBJECT :7:2105058535:0

Notez que le verrou Sch-S a été remplacé par un verrou Tab-IS.

Implications et atténuations

Un verrou à partage d'intention (IS) est toujours un verrou très compatible, mais il n'est pas aussi compatible avec la concurrence que Sch-S. La matrice de compatibilité des verrous indique qu'un verrou IS est en conflit avec :

  • Sch-M (modification de schéma) - selon Sch-S
  • BU (mise à jour en masse)
  • X (exclusif)

L'incompatibilité avec les verrous exclusifs (X) signifie qu'une lecture sous RCSI sera bloquée si un processus concurrent détient un verrou exclusif sur la même ressource. De même, un écrivain qui a besoin d'un verrou exclusif se bloquera si un lecteur RCSI concurrent détient un verrou IS. Des verrous exclusifs sont obtenus chaque fois que des données sont modifiées et conservés jusqu'à la fin de la transaction, de sorte que l'effet du bogue est que les lecteurs sous RCSI seront bloqués par les auteurs simultanés (et vice versa) alors qu'ils ne l'étaient pas avant l'application de CU11.

Un facteur atténuant important est que le bogue ne provoque qu'un niveau table verrou partagé d'intention à prendre. Un écrivain simultané qui a besoin d'un table-level le verrouillage exclusif entraînera un blocage (et potentiellement un interblocage). Cependant, les rédacteurs simultanés qui ne nécessitent que des verrous exclusifs à un niveau inférieur (par exemple, ligne, page ou partition) ne seront pas provoquer un blocage ou une impasse. Au niveau de la table, ces rédacteurs n'acquerront qu'un verrou exclusif à l'intention (IX), compatible avec Tab-IS. Les verrous exclusifs pris à des niveaux de granularité inférieurs ne provoqueront pas de conflit.

Dans la plupart des systèmes, les verrous exclusifs au niveau de la table (Tab-X) seront relativement rares. Sauf demande explicite à l'aide d'un indice TABLOCKX, certaines causes possibles d'un verrou Tab-X sont :

  • Verrouiller l'escalade à partir d'une granularité inférieure
  • Utilisation de SERIALIZABLE sans index de prise en charge pour les verrous de plage de clés

Une solution technique consiste à ajouter l'indicateur de table (redondant) WITH (READCOMMITTED) à chaque table dans chaque requête qui s'exécute sous RCSI. Cela permet de contourner le bogue, de sorte que seul un verrou Sch-S est pris, mais ce n'est guère une proposition pratique.

Malgré ces atténuations, prendre Tab-IS pour une requête en lecture seule sous RCSI est toujours un comportement incorrect. J'espère que cela pourra être corrigé pour SQL Server 2008 R2 avant la sortie du Service Pack 3.

Le bogue "Interblocage incorrect"

Comme mentionné précédemment, le bogue RCSI est introduit en tant qu'effet secondaire d'un correctif pour un bogue de "blocage incorrect". Ce problème antérieur est documenté pour SQL Server 2008 R2 dans KB2929464 et pour SQL Server 2012 dans KB2923460. Aucun des deux documents n'est un modèle de clarté (ou d'exactitude), mais le problème sous-jacent est assez intéressant, donc je veux passer un peu de temps à l'examiner ici.

Essentiellement, le blocage se produit lorsque :

  • Trois transactions simultanées ou plus lues à partir de la même table
  • Les conseils UPDLOCK et TABLOCK sont utilisés dans les trois cas
  • Le paramètre de base de données READ_COMMITTED_SNAPSHOT est activé

Notez que le niveau d'isolement sous lequel les transactions s'exécutent n'a pas d'importance. Pour reproduire le bogue, exécutez d'abord le script d'installation ci-dessous :

USE master;
GO
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
GO
CREATE DATABASE IncorrectDeadlock;
GO
ALTER DATABASE IncorrectDeadlock 
SET READ_COMMITTED_SNAPSHOT ON;
GO
USE IncorrectDeadlock;
GO
CREATE TABLE dbo.Test
(
    id integer IDENTITY NOT NULL, 
    col1 integer NOT NULL,
 
    CONSTRAINT PK_Test
    PRIMARY KEY CLUSTERED (id)
);
GO
INSERT dbo.Test 
    (col1) 
VALUES 
    (1);

Ensuite, exécutez le script suivant dans trois connexions distinctes (notez que la transaction est laissée ouverte) :

USE IncorrectDeadlock;
GO
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
GO
BEGIN TRANSACTION;
SELECT
    T.id,
    T.col1
FROM dbo.Test AS T
    WITH (UPDLOCK, TABLOCK);

À ce stade, la première session aura renvoyé un ensemble de résultats et les deux autres seront bloquées. Le « blocage incorrect » survient lorsque la première session termine sa transaction (soit en validant, soit en annulant). Lorsque cela se produit, l'une des deux autres sessions signale un blocage :

Le blocage se produit parce que les deux sessions précédemment bloquées détiennent Tab-IX (exclusif au niveau de la table) et veulent tous deux convertir leur verrou en Tab-X (exclusif au niveau de la table). Tab-IX est compatible avec un autre Tab-IX, mais pas Tab-X. Il s'agit d'un blocage de conversion (et l'ironie ici est que UPDLOCK est souvent utilisé pour éviter les blocages de conversion).

N'hésitez pas à faire varier le niveau d'isolement des transactions pour les trois requêtes comme vous le souhaitez. Le blocage se produira malgré tout, tant que RCSI est activé, avec les mêmes verrous impliqués. Une fois les tests terminés, supprimez la base de données de test :

USE IncorrectDeadlock;
 
ALTER DATABASE IncorrectDeadlock
SET SINGLE_USER 
WITH ROLLBACK IMMEDIATE;
 
USE master;
 
DROP DATABASE IncorrectDeadlock;

Analyse et explication

Personnellement, je ne me souviens pas avoir utilisé UPDLOCK et TABLOCK ensemble dans mon code. Pour moi, cette combinaison d'indices semble intuitivement étrange car SQL Server n'a pas de verrou de mise à jour au niveau de la table . Alors, qu'est-ce que cela signifie spécifier les conseils UPDLOCK et TABLOCK ensemble ?

La documentation a ceci à dire :

UPDLOCK

Spécifie que les verrous de mise à jour doivent être pris et maintenus jusqu'à la fin de la transaction. UPDLOCK prend des verrous de mise à jour pour les opérations de lecture uniquement au niveau de la ligne ou de la page. Si UPDLOCK est combiné avec TABLOCK, ou si un verrou au niveau de la table est pris pour une autre raison, un verrou exclusif (X) sera pris à la place.

Cela suggère que la combinaison d'indices devrait aboutir à un seul verrou de table exclusif. En fait, ce n'est pas tout à fait tout :

Dans SQL Server 2000, la combinaison des indicateurs UPDLOCK et TABLOCK entraîne la prise de Tab-S (un verrou de table partagé) suivi de la conversion en Tab-X (verrou de table exclusif) sous tous les niveaux d'isolement sauf READ UNCOMMITTED. Cette séquence de verrous peut entraîner un blocage où trois sessions ou plus sont impliquées :deux sessions acquièrent Tab-S et les deux attendent l'autre pour se convertir en Tab-X. Sous READ UNCOMMITTED, SQL Server 2000 prend Sch-S puis Tab-X, qui n'est pas sujet au blocage (juste un blocage normal).

Dans SQL Server 2005 et versions ultérieures (sans le correctif de bogue), les verrous pris dépendent uniquement si RCSI est activé ou non. Si RCSI est activé, tous les niveaux d'isolement prendre Tab-IX puis convertir en Tab-X. Cette séquence provoque le blocage des adresses de correction de bogue.

Si RCSI n'est pas activé, les niveaux d'isolement correspondants se comportent comme ils le faisaient sous SQL Server 2000 (en prenant Tab-S puis en convertissant en Tab-X). Le niveau d'isolement d'instantané (nouveau pour 2005) prend Sch-S suivi de Tab-X. Par conséquent, SI et READ UNCOMMITTED sont les seuls niveaux d'isolement qui ne sont pas sujets à ce blocage dans le scénario UPDLOCK, TABLOCK lorsque RCSI n'est pas activé.

Le correctif de l'impasse

Le correctif modifie les verrous pris lorsque UPDLOCK et TABLOCK sont spécifiés ensemble, pour tous les niveaux d'isolement , et peu importe si RCSI est activé ou non. Une fois le correctif appliqué, UPDLOCK et TABLOCK amènent le moteur à acquérir Tab-SIX (partagé au niveau de la table avec intention exclusive), qui est ensuite converti en Tab-X.

Cela évite le scénario de blocage car Tab-SIX est incompatible avec un autre Tab-SIX. N'oubliez pas que le blocage s'est produit lorsque deux processus ont maintenu Tab-IX en attente de conversion en Tab-X. Avec Tab-IX remplacé par Tab-SIX, il n'est pas possible pour les deux de tenir Tab-SIX en même temps. Le résultat est un scénario de blocage normal au lieu d'un blocage.

Réflexions finales

Le correctif "blocage incorrect" résout un scénario de blocage particulier, mais il n'entraîne toujours pas le comportement que j'imagine les personnes spécifiant UPDLOCK et TABLOCK envisagé. Si SQL Server avait un verrou Tab-U (mise à jour au niveau de la table), il empêcherait les modifications simultanées de la table mais autoriserait les lecteurs simultanés. C'est ce que j'imagine que l'intention des personnes utilisant ces conseils ensemble serait, et je peux voir comment cela pourrait être utile.

L'implémentation actuelle (où Tab-X est finalement pris à la place du Tab-U manquant) ne correspond pas à cette attente car Tab-X empêche les lectures simultanées (sauf si un niveau d'isolement de version de ligne est utilisé). Nous pourrions tout aussi bien spécifier TABLOCKX dans de nombreux cas. Le fait que le correctif introduit également un nouveau bogue (pour les utilisateurs de SQL Server 2008 R2 uniquement) est également regrettable, en particulier si le bogue est inclus dans 2008 R2 SP3.

Notez que le correctif de blocage n'est pas disponible pour les versions de SQL Server antérieures à 2008 R2. Ces versions continueront d'avoir le comportement de verrouillage complexe pour UPDLOCK et TABLOCK comme décrit ci-dessus.

Mes remerciements à Eugene Karpovich qui a le premier attiré mon attention sur ce problème dans un commentaire à mon article sur les modifications de données sous RCSI.