Database
 sql >> Datenbank >  >> RDS >> Database

Der 2008 R2 Bugfix, der RCSI bricht

Einer der im kumulativen Update 11 für SQL Server 2008 R2 Service Pack 2 enthaltenen Fixes behebt einen „falschen Deadlock“, der in einem bestimmten Szenario auftreten kann (weiter unten in diesem Artikel erläutert). Leider führt der Fix einen neuen Fehler ein, bei dem SELECT-Abfragen unter RCSI (Read Committed Snapshot Isolation) damit beginnen, Intent-Shared-Sperren auf Tabellenebene zu nehmen. Infolgedessen sehen Sie nach der Anwendung von 2008 R2 SP2 CU11 (oder höher) möglicherweise eine erhöhte Blockierung (und möglicherweise Deadlocks) für RCSI-Abfragen.

Dies wird eine unwillkommene Überraschung für jeden sein, der daran gewöhnt ist, dass Leser Schreiber nicht blockieren (und umgekehrt), wenn RCSI verwendet wird. Zum Zeitpunkt des Schreibens gibt es keine Lösung für den RCSI-Fehler. Tatsächlich wurde das Connect-Element, das von Eugene Karpovich erstellt wurde, um das Problem zu melden, als „Wird nicht behoben“ geschlossen, obwohl ich weiß, dass diese Entscheidung derzeit überprüft wird.

Normalerweise ist dieses Problem möglicherweise kein so großes Problem, da kumulative Updates im Allgemeinen nicht so weit verbreitet sind wie vollständige Service Packs. Allerdings hat Microsoft kürzlich angekündigt, dass es ein Final Service Pack 3 für SQL Server 2008 R2 geben wird. Dieses Service Pack ist ein einfaches Rollup vorhandener kumulativer SP2-Updates (bis einschließlich CU13), jedoch ohne neue Fixes. Das Ergebnis all dessen ist, dass Benutzer, die SP3 anwenden, plötzlich von dem in CU11 eingeführten RCSI-Fehler betroffen sind, sofern sich in der Zwischenzeit nichts ändert.

Bearbeiten:Kurz bevor dieser Artikel veröffentlicht wurde, hat Microsoft bestätigt, dass diese Regression in SP3 behoben wird.

Derselbe „falsche Deadlock“-Bug (dessen Fix den neuen Bug einführt) wurde auch im kumulativen Update 8 für SQL Server 2012 Service Pack 1 behoben, wie in KB2923460 beschrieben. Der Fix für SQL Server 2012 ist anders und nicht Einführung des neuen RCSI-Problems.

SQL Server 2014 war, soweit ich das beurteilen kann, nie von beiden Problemen betroffen. Es gibt sicherlich keine Dokumentation, die auf etwas anderes hinweist, und die Tests, die ich auf 2014 RTM, CU1 und CU2 durchgeführt habe, reproduzieren keinen der beiden Fehler.

Der R2 RCSI-Fehler von 2008

Eine SELECT-Abfrage, die unter RCSI ausgeführt wird, verwendet normalerweise nur eine Schemastabilitätssperre (Sch-S), die mit allen anderen Sperren außer einer Schemamodifikationssperre (Sch-M) kompatibel ist. Wenn CU11 (oder höher) auf eine SQL Server 2008 R2-Instanz angewendet wird, beginnen diese Abfragen damit, eine Absichtsfreigabesperre (Tab-IS) auf Tabellenebene anzunehmen. Das folgende Testskript kann verwendet werden, um die unterschiedlichen Verhaltensweisen zu demonstrieren:

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;

Bei der Ausführung für eine Instanz von SQL Server 2008 R2 ohne den Fehler zeigt die Debugausgabe wie erwartet eine einzelne Sch-S-Sperre für die Testanweisung:

Prozess erwirbt Sch-S-Sperre auf OBJEKT:7:2105058535:0 Ergebnis:OK
Prozess gibt Sperre auf OBJEKT frei:7:2105058535:0

Bei Ausführung mit SQL Server 2008 R2 Build 10.50.4302 (oder höher) sieht die Ausgabe ähnlich aus wie:

Prozess erwirbt IS-Sperre auf OBJEKT:7:2105058535:0 Ergebnis:OK
Prozess gibt Sperre auf OBJEKT frei:7:2105058535:0

Beachten Sie, dass die Sch-S-Sperre durch eine Tab-IS-Sperre ersetzt wurde.

Auswirkungen und Schadensbegrenzungen

Eine Intent-Shared (IS)-Sperre ist immer noch eine sehr kompatible Sperre, aber sie ist nicht ganz so parallelitätsfreundlich wie Sch-S. Die Kompatibilitätsmatrix für Sperren zeigt, dass eine IS-Sperre in Konflikt steht mit:

  • Sch-M (Schemamodifikation) – nach Sch-S
  • BU (Massenaktualisierung)
  • X (exklusiv)

Die Inkompatibilität mit exklusiven (X) Sperren bedeutet, dass ein Lesevorgang unter RCSI blockiert, wenn ein gleichzeitiger Prozess eine exklusive Sperre für dieselbe Ressource hält. Ebenso blockiert ein Schreiber, der eine exklusive Sperre benötigt, wenn ein gleichzeitiger RCSI-Leser eine IS-Sperre hält. Exkusive Sperren werden immer dann erhalten, wenn Daten geändert werden, und bis zum Ende der Transaktion gehalten. Der Effekt des Fehlers ist also, dass Leser unter RCSI von gleichzeitigen Schreibern (und umgekehrt) blockiert werden, wenn dies vor der Anwendung von CU11 nicht der Fall war.

Ein wesentlicher mildernder Faktor ist, dass der Fehler nur eine Tabellenebene verursacht Intent-Shared-Sperre, die genommen werden soll. Ein gleichzeitiger Schreiber, der eine Tabellenebene benötigt Die exklusive Sperre führt zu einer Blockierung (und möglicherweise zu einem Deadlock). Gleichzeitige Schreiber, die nur exklusive Sperren auf einer niedrigeren Ebene (z. B. Zeilen-, Seiten- oder Partitionsebene) benötigen, werden dies jedoch nicht tun Blockierung oder Deadlock verursachen. Auf Tabellenebene erhalten diese Writer nur eine Intent-Exclusive (IX)-Sperre, die mit Tab-IS kompatibel ist. Die exklusiven Sperren, die auf niedrigeren Granularitätsebenen vorgenommen werden, verursachen keinen Konflikt.

In den meisten Systemen sind exklusive Sperren auf Tabellenebene (Tab-X) relativ ungewöhnlich. Sofern nicht ausdrücklich mit einem TABLOCKX-Hinweis angefordert, sind einige mögliche Ursachen für eine Tab-X-Sperre:

  • Sperren Sie die Eskalation von einer niedrigeren Granularität aus
  • Verwenden von SERIALIZABLE ohne unterstützenden Index für Schlüsselbereichssperren

Eine technische Problemumgehung besteht darin, den (redundanten) Tabellenhinweis WITH (READCOMMITTED) hinzuzufügen zu jeder Tabelle in jeder Abfrage, die unter RCSI ausgeführt wird. Dies geschieht, um den Fehler zu umgehen, sodass nur eine Sch-S-Sperre verwendet wird, aber es ist kaum ein praktischer Vorschlag.

Trotz dieser Abschwächungen ist das Verwenden von Tab-IS für eine schreibgeschützte Abfrage unter RCSI immer noch ein falsches Verhalten. Ich hoffe, dass es für SQL Server 2008 R2 behoben werden kann, bevor Service Pack 3 veröffentlicht wird.

Der "Falsche Deadlock"-Bug

Wie bereits erwähnt, wird der RCSI-Fehler als Nebeneffekt einer Korrektur für einen "falschen Deadlock"-Fehler eingeführt. Dieses frühere Problem ist für SQL Server 2008 R2 in KB2929464 und für SQL Server 2012 in KB2923460 dokumentiert. Keines der Dokumente ist ein Modell für Klarheit (oder Genauigkeit), aber das zugrunde liegende Problem ist ziemlich interessant, daher möchte ich ein wenig Zeit damit verbringen, es hier zu betrachten.

Im Wesentlichen tritt der Deadlock auf, wenn:

  • Drei oder mehr gleichzeitige Transaktionen, die aus derselben Tabelle gelesen werden
  • Die Hinweise UPDLOCK und TABLOCK werden in allen drei Fällen verwendet
  • Die Datenbankeinstellung READ_COMMITTED_SNAPSHOT ist EIN

Beachten Sie, dass es keine Rolle spielt, unter welcher Isolationsstufe die Transaktionen ausgeführt werden. Um den Fehler zu reproduzieren, führen Sie zuerst das folgende Setup-Skript aus:

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

Führen Sie als Nächstes das folgende Skript in drei separaten Verbindungen aus (beachten Sie, dass die Transaktion offen bleibt):

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

An diesem Punkt hat die erste Sitzung eine Ergebnismenge zurückgegeben und die anderen beiden werden blockiert. Der "falsche Deadlock" entsteht, wenn die erste Sitzung ihre Transaktion abschließt (entweder Commit oder Rollback). In diesem Fall meldet eine der beiden anderen Sitzungen einen Deadlock:

Der Deadlock tritt auf, weil die beiden zuvor blockierten Sitzungen Tab-IX (intent-exklusiv auf Tabellenebene) enthalten und beide ihre Sperre in Tab-X (exklusiv auf Tabellenebene) umwandeln möchten. Tab-IX ist mit einem anderen Tab-IX kompatibel, nicht jedoch mit Tab-X. Dies ist ein Konvertierungs-Deadlock (und die Ironie dabei ist, dass UPDLOCK oft verwendet wird, um Konvertierungs-Deadlocks zu vermeiden).

Sie können die Transaktionsisolationsstufe für die drei Abfragen nach Belieben variieren. Der Deadlock tritt unabhängig davon auf, solange RCSI aktiviert ist, wobei dieselben Sperren involviert sind. Wenn die Tests abgeschlossen sind, entfernen Sie die Testdatenbank:

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

Analyse und Erklärung

Ich persönlich kann mich nicht erinnern, jemals UPDLOCK und TABLOCK zusammen in meinem Code verwendet zu haben. Für mich erscheint diese Kombination von Hinweisen intuitiv seltsam, da SQL Server keine Aktualisierungssperre auf Tabellenebene hat . Also, was bedeutet es überhaupt bedeutet UPDLOCK- und TABLOCK-Hinweise zusammen angeben?

Die Dokumentation hat folgendes zu sagen:

UPDLOCK

Gibt an, dass Aktualisierungssperren ergriffen und gehalten werden sollen, bis die Transaktion abgeschlossen ist. UPDLOCK nimmt Aktualisierungssperren für Lesevorgänge nur auf Zeilen- oder Seitenebene. Wenn UPDLOCK mit TABLOCK kombiniert wird oder aus einem anderen Grund eine Sperre auf Tabellenebene gesetzt wird, wird stattdessen eine exklusive (X) Sperre gesetzt.

Dies deutet darauf hin, dass die Hinweiskombination zu einer einzigen exklusiven Tabellensperre führen sollte. Tatsächlich ist dies nicht ganz die ganze Geschichte:

In SQL Server 2000 führt das Kombinieren von UPDLOCK- und TABLOCK-Hinweisen dazu, dass Tab-S (eine gemeinsam genutzte Tabellensperre) verwendet wird, gefolgt von einer Konvertierung in Tab-X (exklusive Tabellensperre) unter allen Isolationsstufen außer READ UNCOMMITTED. Diese Abfolge von Sperren kann zu einem Deadlock führen, wenn drei oder mehr Sitzungen beteiligt sind:Zwei Sitzungen erwerben Tab-S und beide warten darauf, dass die andere in Tab-X konvertiert wird. Unter READ UNCOMMITTED nimmt SQL Server 2000 Sch-S und dann Tab-X, was nicht anfällig für Deadlocks ist (nur normales Blockieren).

Ab SQL Server 2005 (ohne Bugfix) hängen die getroffenen Sperren nur ab ob RCSI aktiviert ist oder nicht. Wenn RCSI aktiviert ist, alle Isolationsstufen Nehmen Sie Tab-IX und konvertieren Sie es dann in Tab-X. Diese Sequenz verursacht den Deadlock der Fehlerbehebungsadressen.

Wenn RCSI nicht aktiviert ist, verhalten sich die übereinstimmenden Isolationsstufen wie unter SQL Server 2000 (Tab-S nehmen und dann in Tab-X konvertieren). Die (neu für 2005) Snapshot-Isolationsstufe nimmt Sch-S gefolgt von Tab-X an. Folglich sind SI und READ UNCOMMITTED die einzigen Isolationsebenen, die nicht für diesen Deadlock im UPDLOCK-, TABLOCK-Szenario anfällig sind, wenn RCSI nicht aktiviert ist.

Der Deadlock-Fix

Der Fix ändert die Sperren, die ergriffen werden, wenn UPDLOCK und TABLOCK zusammen angegeben werden, für alle Isolationsstufen , und egal ob RCSI aktiviert ist oder nicht. Nachdem der Fix angewendet wurde, bewirken UPDLOCK und TABLOCK, dass die Engine Tab-SIX (auf Tabellenebene freigegeben mit Absicht exklusiv) erfasst, das dann in Tab-X konvertiert wird.

Dadurch wird das Deadlock-Szenario vermieden, da Tab-SIX nicht mit einem anderen Tab-SIX kompatibel ist. Denken Sie daran, dass der Deadlock auftrat, als zwei Prozesse Tab-IX darauf warteten, in Tab-X konvertiert zu werden. Da Tab-IX durch Tab-SIX ersetzt wurde, ist es nicht möglich, dass beide gleichzeitig Tab-SIX halten. Das Ergebnis ist ein normales Blockierungsszenario anstelle eines Deadlocks.

Abschließende Gedanken

Die Korrektur des "falschen Deadlocks" löst zwar ein bestimmtes Deadlock-Szenario, führt aber immer noch nicht zu dem Verhalten, von dem ich mir vorstelle, dass die Personen, die UPDLOCK und TABLOCK angeben, vorgesehen sind. Wenn SQL Server über eine Tab-U-Sperre (Aktualisierung auf Tabellenebene) verfügte, würde dies gleichzeitige Änderungen an der Tabelle verhindern, aber gleichzeitige Leser zulassen. Das ist, was ich mir vorstelle, die Absicht der Leute, die diese Hinweise zusammen verwenden, und ich kann sehen, wie nützlich es sein könnte.

Die aktuelle Implementierung (bei der letztendlich Tab-X anstelle des fehlenden Tab-U verwendet wird) entspricht nicht dieser Erwartung, da Tab-X gleichzeitige Lesevorgänge verhindert (es sei denn, es wird eine Isolationsstufe für die Zeilenversionsverwaltung verwendet). Wir könnten in vielen Fällen genauso gut TABLOCKX spezifizieren. Die Tatsache, dass der Fix auch einen neuen Fehler einführt (nur für Benutzer von SQL Server 2008 R2), ist ebenfalls bedauerlich, insbesondere wenn der Fehler weiterhin in 2008 R2 SP3 enthalten ist.

Beachten Sie, dass der Deadlock-Fix nicht für SQL Server-Versionen vor 2008 R2 verfügbar gemacht wird. Diese Versionen haben weiterhin das oben beschriebene komplexe Sperrverhalten für UPDLOCK und TABLOCK.

Mein Dank geht an Eugene Karpovich, der mich zuerst in einem Kommentar zu meinem Artikel über Datenmodifikationen unter RCSI auf dieses Problem aufmerksam gemacht hat.