TL; DR: Die folgende Frage lautet: Gibt es beim Einfügen einer Zeile ein Zeitfenster zwischen der Generierung eines neuen Identity
Werts und dem Sperren des entsprechenden Zeilenschlüssels im Clustered-Index, in dem ein externer Beobachter einen neueren sehen könnte Identity
Wert, der von einer gleichzeitigen Transaktion eingefügt wurde? (In SQL Server.)
Ausführliche Version
Ich habe eine SQL Server-Tabelle mit einer Identity
Spalte namens CheckpointSequence
, die den Schlüssel des Clustered-Index der Tabelle darstellt (der auch eine Reihe zusätzlicher Nonclustered-Indizes enthält). Zeilen werden von mehreren gleichzeitig ablaufenden Prozessen und Threads (auf Isolationsstufe und ohne ) in die Tabelle eingefügt . Gleichzeitig werden in regelmäßigen Abständen Zeilen aus dem Clustered-Index gelesen , die nach dieser Spalte geordnet sind (ebenfalls auf Isolationsstufe , wobei die Option deaktiviert ist).READ COMMITTED
IDENTITY_INSERT
CheckpointSequence
READ COMMITTED
READ COMMITTED SNAPSHOT
Ich verlasse mich derzeit auf die Tatsache, dass der Lesevorgang niemals einen Checkpoint "überspringen" kann. Meine Frage ist: Kann ich mich auf diese Immobilie verlassen? Und wenn nicht, was könnte ich tun, um es wahr zu machen?
Beispiel: Wenn Zeilen mit den Identitätswerten 1, 2, 3, 4 und 5 eingefügt werden, darf der Leser die Zeile mit dem Wert 5 nicht sehen, bevor die Zeile mit dem Wert 4 angezeigt wird. Tests zeigen, dass die Abfrage, die eine ORDER BY CheckpointSequence
Klausel enthält ( und eine WHERE CheckpointSequence > -1
Klausel) blockiert zuverlässig, wann immer Zeile 4 gelesen, aber noch nicht festgeschrieben werden soll, selbst wenn Zeile 5 bereits festgeschrieben wurde.
Ich glaube, dass zumindest theoretisch eine Race Condition vorliegt, die dazu führen könnte, dass diese Annahme gebrochen wird. Leider Identity
sagt die Dokumentation zu nicht viel darüber aus, wie Identity
im Kontext mehrerer gleichzeitiger Transaktionen gearbeitet wird. Sie sagt nur "Jeder neue Wert wird basierend auf dem aktuellen Startwert und dem aktuellen Inkrement generiert." und "Jeder neue Wert für eine bestimmte Transaktion unterscheidet sich von anderen gleichzeitigen Transaktionen in der Tabelle." ( MSDN )
Meine Überlegung ist, es muss irgendwie so funktionieren:
- Eine Transaktion wird gestartet (entweder explizit oder implizit).
- Ein Identitätswert (X) wird generiert.
- Die entsprechende Zeilensperre wird für den Clustered-Index basierend auf dem Identitätswert verwendet (es sei denn, die Sperreneskalation wird aktiviert. In diesem Fall ist die gesamte Tabelle gesperrt).
- Die Zeile wird eingefügt.
- Die Transaktion wird festgeschrieben (möglicherweise viel Zeit später), sodass die Sperre wieder aufgehoben wird.
Ich denke, zwischen Schritt 2 und 3 gibt es ein sehr kleines Fenster, in dem
- Eine gleichzeitige Sitzung könnte den nächsten Identitätswert (X + 1) generieren und alle verbleibenden Schritte ausführen.
- Auf diese Weise kann ein Leser, der genau zu diesem Zeitpunkt kommt, den Wert X + 1 lesen, wobei der Wert von X fehlt.
Natürlich scheint die Wahrscheinlichkeit dafür äußerst gering zu sein; aber dennoch - es könnte passieren. Oder könnte es?
(Wenn Sie sich für den Kontext interessieren: Dies ist die Implementierung der SQL Persistence Engine von NEventStore . NEventStore implementiert einen Nur-Anhängen-Ereignisspeicher, in dem jedes Ereignis eine neue aufsteigende Prüfpunktsequenznummer erhält. Clients lesen Ereignisse aus dem Ereignisspeicher, sortiert nach Prüfpunkt Wenn ein Ereignis mit Checkpoint X verarbeitet wurde, berücksichtigen Clients nur "neuere" Ereignisse, dh Ereignisse mit Checkpoint X + 1 und höher. Daher ist es wichtig, dass Ereignisse niemals übersprungen werden. Ich versuche derzeit festzustellen, ob die auf Identity
-basierende Checkpoint-Implementierung diese Anforderung erfüllt. Dies sind die genauen verwendeten SQL-Anweisungen : Schema , Writer-Abfrage ,Leseranfrage .)
Wenn ich recht habe und die oben beschriebene Situation eintreten könnte, sehe ich nur zwei Möglichkeiten, mit ihnen umzugehen, die beide unbefriedigend sind:
- Wenn Sie einen Prüfpunktsequenzwert X + 1 sehen, bevor Sie X gesehen haben, schließen Sie X + 1 und versuchen Sie es später erneut. Da es
Identity
jedoch natürlich zu Lücken kommen kann (z. B. wenn die Transaktion zurückgesetzt wird), kommt X möglicherweise nie. - Also der gleiche Ansatz, aber akzeptiere die Lücke nach n Millisekunden. Welchen Wert von n soll ich jedoch annehmen?
Irgendwelche besseren Ideen?