sql >> Databasteknik >  >> RDS >> Database

2008 års R2 Bugfix som bryter RCSI

En av korrigeringarna som ingår i kumulativ uppdatering 11 för SQL Server 2008 R2 Service Pack 2 adresserar ett "felaktigt dödläge" som kan uppstå i ett specifikt scenario (förklaras senare i den här artikeln). Tyvärr introducerar korrigeringen en ny bugg, där SELECT-frågor under RCSI (läs committed snapshot isolation) börjar ta avsiktsdelade lås på tabellnivå. Som en konsekvens kan du se ökad blockering (och potentiellt dödläge) för RCSI-frågor efter att ha tillämpat 2008 R2 SP2 CU11 (eller senare).

Detta kommer som en ovälkommen överraskning för alla som är vana vid att läsare inte blockerar skribenter (och vice versa) när de använder RCSI. Det finns ingen fix för RCSI-buggen i skrivande stund. Faktum är att Connect-objektet som skapats av Eugene Karpovich för att rapportera problemet har stängts som "Kommer inte åtgärda", även om jag förstår att detta beslut för närvarande granskas.

Vanligtvis kanske det här problemet inte är ett så stort problem, eftersom kumulativa uppdateringar i allmänhet inte används i lika stor utsträckning som full service pack. Microsoft meddelade dock nyligen att det kommer att finnas ett Final Service Pack 3 för SQL Server 2008 R2. Detta Service Pack kommer att vara en enkel upprullning av befintliga SP2 kumulativa uppdateringar (upp till och inklusive CU13) men utan nya korrigeringar. Resultatet av allt detta är att, om inte något förändras under tiden, kommer användare som använder SP3 plötsligt att börja påverkas av RCSI-felet som introducerades i CU11.

edit:Strax innan den här artikeln publicerades bekräftade Microsoft att denna regression kommer att åtgärdas i SP3.

Samma "felaktiga dödläge"-bugg (vars korrigering introducerar den nya buggen) fixades också i Kumulativ uppdatering 8 för SQL Server 2012 Service Pack 1 som beskrivs i KB2923460. Korrigeringen för SQL Server 2012 är annorlunda och gör det inte introducera det nya RCSI-problemet.

SQL Server 2014 påverkades aldrig av något av problemen, så vitt jag kan säga. Det finns verkligen ingen dokumentation som indikerar något annat, och testerna jag har utfört på 2014 RTM, CU1 och CU2 reproducerar inte någon av buggen.

2008 års R2 RCSI-fel

En SELECT-fråga som körs under RCSI tar vanligtvis bara ett schemastabilitetslås (Sch-S), vilket är kompatibelt med alla andra lås förutom ett schemamodifieringslås (Sch-M). När CU11 (eller senare) tillämpas på en SQL Server 2008 R2-instans, börjar dessa frågor att ta ett tabell-nivå avsiktsdelat (Tab-IS) lås. Följande testskript kan användas för att visa skillnaden i beteenden:

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;

När den körs mot en instans av SQL Server 2008 R2 utan felet visar felsökningsutgången ett enda Sch-S-lås som tagits för testsatsen som förväntat:

Process att hämta Sch-S-lås på OBJECT:7:2105058535:0 resultat:OK
Process att frigöra lås på OBJECT:7:2105058535:0

När den körs mot SQL Server 2008 R2 build 10.50.4302 (eller högre) liknar resultatet:

Processinhämtning av IS-lås på OBJECT:7:2105058535:0 resultat:OK
Process att frigöra lås på OBJECT:7:2105058535:0

Observera att Sch-S-låset har ersatts av ett Tab-IS-lås.

Konsekvenser och begränsningar

Ett avsiktsdelat (IS)-lås är fortfarande ett mycket kompatibelt lås, men det är inte riktigt lika samtidighetsvänligt som Sch-S. Låskompatibilitetsmatrisen visar att ett IS-lås står i konflikt med:

  • Sch-M (schemamodifiering) – enligt Sch-S
  • BU (bulkuppdatering)
  • X (exklusivt)

Inkompatibiliteten med exklusiva (X) lås innebär att en läsning under RCSI kommer att blockeras om en samtidig process har ett exklusivt lås på samma resurs. På samma sätt kommer en skribent som behöver ett exklusivt lås att blockera om en samtidig RCSI-läsare har ett IS-lås. Ursäkta lås erhålls närhelst data modifieras och hålls till slutet av transaktionen, så effekten av buggen är att läsare under RCSI kommer att blockeras av samtidiga skribenter (och vice versa) när de inte var innan CU11 applicerades.

En betydande förmildrande faktor är att felet bara orsakar en tabellnivå avsiktligt delat lås som ska tas. En samtidig skribent som behöver en tabellnivå exklusivt lås kommer att orsaka blockering (och potentiellt ett dödläge). Men samtidiga skribenter som bara kräver exklusiva lås på en lägre nivå (t.ex. rad, sida eller partition) inte orsaka blockering eller dödläge. På tabellnivå kommer dessa skribenter endast att skaffa ett avsiktsexklusivt (IX) lås, som är kompatibelt med Tab-IS. De exklusiva låsen som tas med lägre granularitetsnivåer kommer inte att orsaka en konflikt.

I de flesta system kommer exklusiva lås på bordsnivå (Tab-X) att vara relativt ovanliga. Om det inte uttryckligen begärs med hjälp av en TABLOCKX-tips, är några möjliga orsaker till ett Tab-X-lås:

  • Lås eskalering från en lägre granularitet
  • Använda SERIALIZABLE utan stödindex för nyckelomfångslås

En teknisk lösning är att lägga till (redundanta) tabelltipset WITH (READCOMMITTED) till varje tabell i varje fråga som körs under RCSI. Detta råkar förbigå felet så bara ett Sch-S-lås tas, men det är knappast ett praktiskt förslag.

Trots dessa begränsningar är det fortfarande ett felaktigt beteende att ta Tab-IS för en skrivskyddad fråga under RCSI. Jag hoppas att det kan fixas för SQL Server 2008 R2 innan Service Pack 3 släpps.

Bugen "Felaktig dödläge"

Som nämnts tidigare introduceras RCSI-buggen som en bieffekt av en fix för ett "felaktigt dödläge". Det här tidigare problemet är dokumenterat för SQL Server 2008 R2 i KB2929464 och för SQL Server 2012 i KB2923460. Inget av dokumenten är en modell för klarhet (eller noggrannhet), men den underliggande frågan är ganska intressant, så jag vill spendera lite tid på att titta på den här.

I huvudsak uppstår dödläget när:

  • Tre eller fler samtidiga transaktioner läses från samma tabell
  • UPDLOCK- och TABLOCK-tipsen används i alla tre fallen
  • Databasinställningen READ_COMMITTED_SNAPSHOT är PÅ

Observera att det inte spelar någon roll vilken isoleringsnivå transaktionerna körs under. För att återskapa felet, kör först installationsskriptet nedan:

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);

Kör sedan följande skript i tre separata anslutningar (observera att transaktionen lämnas öppen):

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);

Vid det här laget kommer den första sessionen att ha returnerat en resultatuppsättning och de andra två kommer att blockeras. Det "felaktiga dödläget" uppstår när den första sessionen slutför sin transaktion (antingen committing eller roll back). När detta inträffar kommer en av de andra två sessionerna att rapportera ett dödläge:

Dödläget uppstår eftersom de två tidigare blockerade sessionerna innehåller Tab-IX (exklusivt för tabellnivå) och båda vill konvertera sitt lås till Tab-X (exklusivt för tabellnivå). Tab-IX är kompatibel med en annan Tab-IX, men inte Tab-X. Detta är ett dödläge för konvertering (och det ironiska här är att UPDLOCK ofta används för att undvika dödläge för konvertering).

Variera gärna transaktionsisoleringsnivån för de tre frågorna som du vill. Deadlocket kommer att inträffa oavsett, så länge som RCSI är aktiverat, med samma lås inblandade. När testerna är klara, ta bort testdatabasen:

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

Analyse och förklaring

Jag minns personligen inte att jag någonsin använt UPDLOCK och TABLOCK tillsammans i min kod. För mig verkar den här kombinationen av tips intuitivt udda eftersom SQL Server inte har något uppdateringslås på tabellnivå . Så vad betyder det ens för att ange UPDLOCK- och TABLOCK-tips tillsammans?

Dokumentationen har detta att säga:

UPDLOCK

Anger att uppdateringslås ska tas och hållas kvar tills transaktionen slutförs. UPDLOCK tar endast uppdateringslås för läsoperationer på radnivå eller sidnivå. Om UPDLOCK kombineras med TABLOCK, eller ett lås på bordsnivå tas av någon annan anledning, kommer ett exklusivt (X) lås att användas istället.

Detta tyder på att tipskombinationen borde resultera i ett enda exklusivt bordslås. Det här är faktiskt inte hela historien:

I SQL Server 2000 resulterar kombinationen av UPDLOCK och TABLOCK-tips i att Tab-S (ett delat tabelllås) tas följt av konvertering till Tab-X (exklusivt tabelllås) under alla isoleringsnivåer utom READ UNCOMMITTED. Denna sekvens av låsningar kan resultera i ett dödläge där tre eller fler sessioner är inblandade:två sessioner förvärvar Tab-S och båda väntar på den andra för att konvertera till Tab-X. Under READ UNCOMMITTED tar SQL Server 2000 Sch-S och sedan Tab-X, vilket inte är benäget att hamna i dödläge (bara normal blockering).

I SQL Server 2005 och framåt (utan buggfix) beror enbart på vilka lås som tas om RCSI är aktiverat eller inte. Om RCSI är aktiverat, alla isoleringsnivåer ta Tab-IX och konvertera sedan till Tab-X. Denna sekvens orsakar det dödläge som buggfixen adresserar.

Om RCSI inte är aktiverat, fungerar de matchande isoleringsnivåerna som de gjorde under SQL Server 2000 (med Tab-S och sedan omvandling till Tab-X). Den (nya för 2005) ögonblicksbildsisoleringsnivån tar Sch-S följt av Tab-X. Som en konsekvens är SI och READ UNCOMMITTED de enda isoleringsnivåerna som inte är utsatta för detta dödläge i UPDLOCK, TABLOCK-scenariot när RCSI inte är aktiverat.

The Deadlock Fix

Korrigeringen ändrar låsen som tas när UPDLOCK och TABLOCK specificeras tillsammans, för alla isoleringsnivåer , och oavsett om RCSI är aktiverat eller inte. Efter att korrigeringen har tillämpats gör UPDLOCK och TABLOCK att motorn skaffar Tab-SIX (tabellnivå delad med avsikt exklusivt), som sedan konverteras till Tab-X.

Detta undviker dödläget eftersom Tab-SIX är inkompatibelt med en annan Tab-SIX. Kom ihåg att dödläget inträffade när två processer höll Tab-IX och väntade på att konvertera till Tab-X. Med Tab-IX ersatt av Tab-SIX är det inte möjligt för båda att hålla Tab-SIX samtidigt. Resultatet är ett normalt blockeringsscenario istället för ett dödläge.

Sluta tankar

Den "felaktiga dödläge"-fixen löser visserligen ett visst dödlägesscenario, men det resulterar fortfarande inte i beteendet som jag föreställer mig att personerna som anger UPDLOCK och TABLOCK tänkt sig. Om SQL Server hade ett Tab-U-lås (uppdatering på tabellnivå), skulle det förhindra samtidiga ändringar av tabellen men tillåta samtidiga läsare. Det här är vad jag föreställer mig avsikten med människor som använder dessa tips tillsammans, och jag kan se hur det kan vara användbart.

Den nuvarande implementeringen (där Tab-X i slutändan tas istället för den saknade Tab-U) matchar inte denna förväntning eftersom Tab-X förhindrar samtidiga läsningar (såvida inte en radversionsisoleringsnivå används). Vi kan lika gärna specificera TABLOCKX i många fall. Det faktum att korrigeringen också introducerar en ny bugg (endast för användare av SQL Server 2008 R2) är också olyckligt, särskilt om buggen fortsätter att inkluderas i 2008 R2 SP3.

Observera att dödlägeskorrigeringen inte görs tillgänglig för SQL Server-versioner före 2008 R2. Dessa versioner kommer att fortsätta att ha det komplexa låsbeteendet för UPDLOCK och TABLOCK som beskrivs ovan.

Mitt tack till Eugene Karpovich som först gjorde mig uppmärksam på denna fråga i en kommentar till min artikel om dataändringar under RCSI.


  1. Följ med oss ​​för en introduktion till åtkomst med SQL Server

  2. SQL Server Hög tillgänglighet:Installera SQL Server-failover-klustrad instans del 2

  3. Hur man hämtar data från markören i Oracle med hjälp av For Loop

  4. Kan inte ansluta till MySQL-serverfel 111