Kann ich mich darauf verlassen, dass die SQL Server-Identitätswerte der Reihe nach gelesen werden?

24

TL; DR: Die folgende Frage lautet: Gibt es beim Einfügen einer Zeile ein Zeitfenster zwischen der Generierung eines neuen IdentityWerts 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 IdentitySpalte 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 COMMITTEDIDENTITY_INSERTCheckpointSequenceREAD COMMITTEDREAD 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 CheckpointSequenceKlausel enthält ( und eine WHERE CheckpointSequence > -1Klausel) 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 Identitysagt die Dokumentation zu nicht viel darüber aus, wie Identityim 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:

  1. Eine Transaktion wird gestartet (entweder explizit oder implizit).
  2. Ein Identitätswert (X) wird generiert.
  3. 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).
  4. Die Zeile wird eingefügt.
  5. 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 Identityjedoch 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?

Fabian Schmied
quelle
Haben Sie versucht, Sequence anstelle von identity zu verwenden? Ich glaube nicht, dass Sie mit Identität zuverlässig vorhersagen können, welche Einfügung einen bestimmten Identitätswert erhalten wird, aber dies sollte kein Problem bei der Verwendung einer Sequenz sein. Das ändert natürlich die Art und Weise, wie Sie jetzt vorgehen.
Antoine Hernandez
@SoleDBAGuy Würde eine Sequenz die oben beschriebene Rennbedingung nicht noch wahrscheinlicher machen? Ich erstelle einen neuen Sequenzwert X (anstelle von Schritt 2 oben) und füge dann eine Zeile ein (Schritte 3 und 4). Zwischen 2 und 3 besteht die Möglichkeit, dass jemand anderes den nächsten Sequenzwert X + 1 erzeugt und festschreibt, und ein Leser liest diesen Wert X + 1, bevor ich überhaupt meine Zeile mit dem Sequenzwert X einfüge.
Fabian Schmied

Antworten:

26

Gibt es beim Einfügen einer Zeile ein Zeitfenster zwischen der Generierung eines neuen Identitätswerts und dem Sperren des entsprechenden Zeilenschlüssels im Clustered-Index, in dem ein externer Beobachter einen neueren Identitätswert sehen kann, der durch eine gleichzeitige Transaktion eingefügt wurde?

Ja.

Die Vergabe von Identitätswerten ist unabhängig von der enthaltenen Benutzertransaktion . Dies ist ein Grund dafür, dass Identitätswerte auch dann verwendet werden, wenn die Transaktion zurückgesetzt wird. Die Inkrementierungsoperation selbst ist durch einen Zwischenspeicher geschützt, um eine Beschädigung zu verhindern. Dies ist jedoch der Umfang der Schutzfunktionen.

Unter bestimmten Umständen Ihrer Implementierung wird die Identitätszuweisung (ein Aufruf von CMEDSeqGen::GenerateNewValue) vorgenommen, bevor die Benutzertransaktion für die Einfügung überhaupt aktiviert wird (und bevor Sperren aufgehoben werden).

Indem ich zwei Einfügungen gleichzeitig mit einem angehängten Debugger ausführte, um einen Thread einzufrieren, nachdem der Identitätswert erhöht und zugewiesen wurde, konnte ich ein Szenario reproduzieren, in dem:

  1. Sitzung 1 erhält einen Identitätswert (3)
  2. Sitzung 2 erhält einen Identitätswert (4)
  3. Sitzung 2 führt die Einfügung durch und schreibt fest (damit Zeile 4 vollständig sichtbar ist)
  4. Sitzung 1 führt die Einfügung durch und schreibt fest (Zeile 3)

Nach Schritt 3 ergab eine Abfrage unter Verwendung von row_number unter Festschreiben des Lesezugriffs Folgendes:

Bildschirmfoto

In Ihrer Implementierung würde dies dazu führen, dass Checkpoint ID 3 falsch übersprungen wird.

Das Fenster der Missmöglichkeit ist relativ klein, aber es existiert. So geben Sie ein realistischeres Szenario an, als wenn ein Debugger angehängt wäre: Ein ausführender Abfragethread kann den Scheduler nach Schritt 1 oben liefern. Auf diese Weise kann ein zweiter Thread einen Identitätswert zuweisen, einfügen und festschreiben, bevor der ursprüngliche Thread seine Einfügung fortsetzt.

Der Übersichtlichkeit halber gibt es keine Sperren oder anderen Synchronisationsobjekte, die den Identitätswert schützen, nachdem er zugewiesen wurde und bevor er verwendet wird. Beispielsweise kann nach Schritt 1 oben eine gleichzeitige Transaktion den neuen Identitätswert mithilfe von T-SQL-Funktionen anzeigen, wie IDENT_CURRENTvor dem Vorhandensein der Zeile in der Tabelle (auch ohne Commit).

Grundsätzlich gibt es nicht mehr Garantien für Identitätswerte als dokumentiert :

  • Jeder neue Wert wird basierend auf dem aktuellen Startwert und Inkrement generiert.
  • Jeder neue Wert für eine bestimmte Transaktion unterscheidet sich von anderen gleichzeitigen Transaktionen in der Tabelle.

Das ist es wirklich.

Wenn eine strikte Transaktions-FIFO-Verarbeitung erforderlich ist, müssen Sie wahrscheinlich manuell serialisieren. Wenn für die Anwendung weniger Anforderungen gelten, stehen Ihnen mehr Optionen zur Verfügung. Die Frage ist in dieser Hinsicht nicht zu 100% klar. Trotzdem finden Sie einige nützliche Informationen in Remus Rusanus Artikel Verwenden von Tabellen als Warteschlangen .

Paul White sagt GoFundMonica
quelle
7

Da Paul White absolut richtig geantwortet hat, besteht die Möglichkeit, vorübergehend Identitätszeilen zu "überspringen". Hier ist nur ein kleiner Code, mit dem Sie diesen Fall für sich selbst reproduzieren können.

Erstellen Sie eine Datenbank und eine Testtabelle:

create database IdentityTest
go
use IdentityTest
go
create table dbo.IdentityTest (ID int identity, c1 char(10))
create clustered index CI_dbo_IdentityTest_ID on dbo.IdentityTest(ID)

Führen Sie gleichzeitige Einfügungen und Auswahlen für diese Tabelle in einem C # -Konsolenprogramm durch:

using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Threading;

namespace IdentityTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var insertThreads = new List<Thread>();
            var selectThreads = new List<Thread>();

            //start threads for infinite inserts
            for (var i = 0; i < 100; i++)
            {
                insertThreads.Add(new Thread(InfiniteInsert));
                insertThreads[i].Start();
            }

            //start threads for infinite selects
            for (var i = 0; i < 10; i++)
            {
                selectThreads.Add(new Thread(InfiniteSelectAndCheck));
                selectThreads[i].Start();
            }
        }

        private static void InfiniteSelectAndCheck()
        {
            //infinite loop
            while (true)
            {
                //read top 2 IDs
                var cmd = new SqlCommand("select top(2) ID from dbo.IdentityTest order by ID desc")
                {
                    Connection = new SqlConnection("Server=localhost;Database=IdentityTest;Integrated Security=SSPI;Application Name=IdentityTest")
                };

                try
                {
                    cmd.Connection.Open();
                    var dr = cmd.ExecuteReader();

                    //read first row
                    dr.Read();
                    var row1 = int.Parse(dr["ID"].ToString());

                    //read second row
                    dr.Read();
                    var row2 = int.Parse(dr["ID"].ToString());

                    //write line if row1 and row are not consecutive
                    if (row1 - 1 != row2)
                    {
                        Console.WriteLine("row1=" + row1 + ", row2=" + row2);
                    }
                }
                finally
                {
                    cmd.Connection.Close();
                }
            }
        }

        private static void InfiniteInsert()
        {
            //infinite loop
            while (true)
            {
                var cmd = new SqlCommand("insert into dbo.IdentityTest (c1) values('a')")
                {
                    Connection = new SqlConnection("Server=localhost;Database=IdentityTest;Integrated Security=SSPI;Application Name=IdentityTest")
                };

                try
                {
                    cmd.Connection.Open();
                    cmd.ExecuteNonQuery();
                }
                finally
                {
                    cmd.Connection.Close();
                }
            }
        }
    }
}

Diese Konsole gibt für jeden Fall eine Zeile aus, wenn einer der Lesethreads einen Eintrag "verfehlt".

Stefan Kainz
quelle
1
Netter Code, aber Sie suchen nur nach fortlaufenden IDs ( "// Zeile schreiben, wenn Zeile1 und Zeile nicht fortlaufend sind" ). Möglicherweise werden Lücken erzeugt, die Ihr Code drucken wird. Das bedeutet nicht, dass diese Lücken später geschlossen werden.
Ypercubeᵀᴹ
1
Da der Code kein Szenario auslöst IDENTITY, in dem Lücken entstehen (z. B. das Zurücksetzen einer Transaktion), werden in den gedruckten Zeilen tatsächlich "übersprungene" Werte angezeigt (oder zumindest, als ich sie auf meinem Computer ausgeführt und überprüft habe). Sehr schönes Repro-Muster!
Fabian Schmied
5

Es ist am besten, nicht zu erwarten, dass die Identitäten aufeinander folgen, da es viele Szenarien gibt, die Lücken lassen können. Es ist besser, die Identität als abstrakte Zahl zu betrachten und ihr keine geschäftliche Bedeutung beizumessen.

Grundsätzlich können Lücken auftreten, wenn Sie INSERT-Vorgänge rückgängig machen (oder Zeilen explizit löschen), und Duplikate können auftreten, wenn Sie die Tabelleneigenschaft IDENTITY_INSERT auf ON setzen.

Lücken können auftreten, wenn:

  1. Datensätze werden gelöscht.
  2. Beim Versuch, einen neuen Datensatz einzufügen, ist ein Fehler aufgetreten (Rollback)
  3. Ein Update / Insert mit explizitem Wert (Option identity_insert).
  4. Inkrementeller Wert ist mehr als 1.
  5. Eine Transaktion wird zurückgesetzt.

Die Identitätseigenschaft für eine Spalte hat nie garantiert:

• Einzigartigkeit

• Aufeinanderfolgende Werte innerhalb einer Transaktion. Wenn die Werte aufeinander folgen müssen, sollte die Transaktion eine exklusive Sperre für die Tabelle verwenden oder die Isolationsstufe SERIALIZABLE verwenden.

• Aufeinanderfolgende Werte nach dem Neustart des Servers.

• Wiederverwendung von Werten.

Wenn Sie aus diesem Grund keine Identitätswerte verwenden können, erstellen Sie eine separate Tabelle mit einem aktuellen Wert und verwalten Sie den Zugriff auf die Tabelle und die Nummernvergabe mit Ihrer Anwendung. Dies kann die Leistung beeinträchtigen.

https://msdn.microsoft.com/en-us/library/ms186775(v=sql.105).aspx
https://msdn.microsoft.com/en-us/library/ms186775(v=sql.110) .aspx

Stacylaray
quelle
Ich denke, Lücken sind nicht mein primäres Problem - mein Hauptproblem ist die steigende Sichtbarkeit von Werten. (Das heißt, der Identitätswert 7 darf für eine Abfrage nach diesem Wert nicht beobachtbar sein, bevor der Identitätswert 6 erreicht ist.)
Fabian Schmied,
1
Ich habe gesehen, wie Identitätswerte wie
folgt festgeschrieben wurden
Sicher, das ist leicht reproduzierbar, z. B. mit dem Szenario aus Lennarts Antwort. Die Frage , die ich mit bin zu kämpfen, ob ich kann beobachten , dass , um zu begehen , wenn eine Abfrage mit einer Verwendung der ORDER BY CheckpointSequenceKlausel (die die Reihenfolge des gruppierten Index sein passiert). Ich denke, es läuft auf die Frage hinaus, ob die Generierung eines Identitätswerts in irgendeiner Weise mit den von der INSERT-Anweisung ausgeführten Sperren zusammenhängt oder ob es sich einfach um zwei voneinander unabhängige Aktionen handelt, die von SQL Server nacheinander ausgeführt werden.
Fabian Schmied
1
Was ist die Abfrage? Wenn Sie read commit verwenden, würde in Ihrem Beispiel order by 1, 2, 3, 5 anzeigen, da sie festgeschrieben wurden und 4 nicht, dh Dirty Read. In Ihrer Erklärung zu NEventStore heißt es außerdem: "Daher ist es wichtig, dass Ereignisse niemals übersprungen werden, da sie nie wieder berücksichtigt werden."
Stacylaray
Die Abfrage ist oben angegeben ( gist.github.com/fschmied/47f716c32cb64b852f90 ) - sie ist ausgelagert, lässt sich aber auf ein einfaches Ergebnis reduzierenSELECT ... FROM Commits WHERE CheckpointSequence > ... ORDER BY CheckpointSequence . Ich glaube nicht, dass diese Abfrage die gesperrte Zeile 4 überschreitet, oder? (In meinen Experimenten wird es blockiert, wenn die Abfrage versucht, die KEY-Sperre für Zeile 4 abzurufen.)
Fabian Schmied,
1

Ich vermute, dass dies gelegentlich zu Problemen führen kann, die sich verschlimmern, wenn der Server stark ausgelastet ist. Betrachten Sie zwei Transaktionen:

  1. T1: Einfügen in T ... - Sagen wir, 5 werden eingefügt
  2. T2: Einfügen in T ... - Sagen wir, 6 werden eingefügt
  3. T2: Festschreiben
  4. Der Leser sieht 6, aber nicht 5
  5. T1: Festschreiben

Im obigen Szenario ist Ihre LAST_READ_ID 6, daher werden 5 niemals gelesen.

Lennart
quelle
Meine Tests scheinen zu zeigen, dass dieses Szenario kein Problem ist, da Reader (Schritt 4) blockiert (bis T1 seine Sperren freigegeben hat), wenn er versucht, die Zeile mit dem Wert 5 zu lesen. Vermisse ich etwas?
Fabian Schmied
Sie könnten Recht haben, ich kenne den Sperrmechanismus in SQL Server nicht so gut (daher vermute ich in meiner Antwort).
Lennart
Hängt von der Isolationsstufe des Lesers ab. Ich sehe beide, blocke oder sehe nur 6.
Michael Green
0

Ausführen dieses Skripts:

BEGIN TRAN;
INSERT INTO dbo.Example DEFAULT VALUES;
COMMIT;

Nachfolgend sind die Sperren aufgeführt, die von einer erweiterten Ereignissitzung erfasst und freigegeben wurden:

name            timestamp                   associated_object_id    mode    object_id   resource_type   session_id  resource_description
lock_acquired   2016-03-29 06:37:28.9968693 1585440722              IX      1585440722  OBJECT          51          
lock_acquired   2016-03-29 06:37:28.9969268 7205759890195415040     IX      0           PAGE            51          1:1235
lock_acquired   2016-03-29 06:37:28.9969306 7205759890195415040     RI_NL   0           KEY             51          (ffffffffffff)
lock_acquired   2016-03-29 06:37:28.9969330 7205759890195415040     X       0           KEY             51          (29cf3326f583)
lock_released   2016-03-29 06:37:28.9969579 7205759890195415040     X       0           KEY             51          (29cf3326f583)
lock_released   2016-03-29 06:37:28.9969598 7205759890195415040     IX      0           PAGE            51          1:1235
lock_released   2016-03-29 06:37:28.9969607 1585440722              IX      1585440722  OBJECT          51      

Beachten Sie die RI_N KEY-Sperre, die unmittelbar vor der X-Tastensperre für die neu erstellte Zeile erworben wurde. Diese kurzlebige Bereichssperre verhindert, dass eine gleichzeitige Einfügung eine andere RI_N KEY-Sperre erhält, da RI_N-Sperren nicht kompatibel sind. Das Fenster, das Sie zwischen den Schritten 2 und 3 erwähnt haben, ist kein Problem, da die Bereichssperre vor der Zeilensperre für den neu generierten Schlüssel aktiviert wird.

Solange SELECT...ORDER BYder Scan vor den gewünschten neu eingefügten Zeilen beginnt, würde ich das gewünschte Verhalten in der Standardisolationsstufe erwarten, READ COMMITTEDsolange die Datenbankoption READ_COMMITTED_SNAPSHOTdeaktiviert ist.

Dan Guzman
quelle
1
Nach technet.microsoft.com/en-us/library/... , zwei Schlösser mit RangeI_Nsind kompatibel , dh blockieren sie nicht (die Sperre meist gibt es auf einem vorhandenen serializable Leser zum Blockieren).
Fabian Schmied
@ FabianSchmied, interessant. Dieses Thema steht in Konflikt mit der Sperrenkompatibilitätsmatrix in technet.microsoft.com/en-us/library/ms186396(v=sql.105).aspx , in der angegeben ist, dass die Sperren nicht kompatibel sind. Das Einfügebeispiel in dem von Ihnen erwähnten Link zeigt dasselbe Verhalten wie in der Ablaufverfolgung in meiner Antwort (kurzlebige Einfügebereichssperre zum Testen des Bereichs vor der exklusiven Tastensperre).
Dan Guzman
1
Eigentlich sagt die Matrix "N" für "kein Konflikt" (nicht für "nicht kompatibel") :)
Fabian Schmied
0

Nach meinem Verständnis von SQL Server zeigt die zweite Abfrage standardmäßig keine Ergebnisse an, bis die erste Abfrage festgeschrieben wurde. Wenn die erste Abfrage ein ROLLBACK anstelle eines COMMIT ausführt, wird in Ihrer Spalte eine fehlende ID angezeigt.

Basiseinstellung

Datenbanktabelle

Ich habe eine Datenbanktabelle mit folgender Struktur angelegt:

CREATE TABLE identity_rc_test (
    ID4VALUE INT IDENTITY (1,1), 
    TEXTVALUE NVARCHAR(20),
    CONSTRAINT PK_ID4_VALUE_CLUSTERED 
        PRIMARY KEY CLUSTERED (ID4VALUE, TEXTVALUE)
)

Isolationsstufe der Datenbank

Ich habe die Isolationsstufe meiner Datenbank mit der folgenden Anweisung überprüft:

SELECT snapshot_isolation_state, 
       snapshot_isolation_state_desc, 
       is_read_committed_snapshot_on
FROM sys.databases WHERE NAME = 'mydatabase'

Welches das folgende Ergebnis für meine Datenbank zurückbrachte:

snapshot_isolation_state    snapshot_isolation_state_desc   is_read_committed_snapshot_on
0                           OFF                             0

(Dies ist die Standardeinstellung für eine Datenbank in SQL Server 2012)

Testskripte

Die folgenden Skripts wurden mit den Standardeinstellungen für SQL Server-SSMS-Clients und den Standardeinstellungen für SQL Server ausgeführt.

Einstellungen für Clientverbindungen

Der Client wurde so eingestellt, dass er die Transaktionsisolationsstufe READ COMMITTEDgemäß den Abfrageoptionen in SSMS verwendet.

Abfrage 1

Die folgende Abfrage wurde in einem Abfragefenster mit der SPID 57 ausgeführt

SELECT * FROM dbo.identity_rc_test
BEGIN TRANSACTION [FIRST_QUERY]
INSERT INTO dbo.identity_rc_test (TEXTVALUE) VALUES ('Nine')
/* Commit is commented out to prevent the INSERT from being commited
--COMMIT TRANSACTION [FIRST_QUERY]
--ROLLBACK TRANSACTION [FIRST_QUERY]
*/

Abfrage 2

Die folgende Abfrage wurde in einem Abfragefenster mit der SPID 58 ausgeführt

BEGIN TRANSACTION [SECOND_QUERY]
INSERT INTO dbo.identity_rc_test (TEXTVALUE) VALUES ('Ten')
COMMIT TRANSACTION [SECOND_QUERY]
SELECT * FROM dbo.identity_rc_test

Die Abfrage wird nicht abgeschlossen und wartet auf die Freigabe der eXclusive-Sperre auf einer SEITE.

Skript zum Ermitteln der Sperrung

Dieses Skript zeigt die Sperrung der Datenbankobjekte für die beiden Transaktionen an:

SELECT request_session_id, resource_type,
       resource_description, 
       resource_associated_entity_id,
       request_mode, request_status
FROM sys.dm_tran_locks
WHERE request_session_id IN (57, 58)

Und hier sind die Ergebnisse:

58  DATABASE                    0                   S   GRANT
57  DATABASE                    0                   S   GRANT
58  PAGE            1:79        72057594040549300   IS  GRANT
57  PAGE            1:79        72057594040549300   IX  GRANT
57  KEY         (a0aba7857f1b)  72057594040549300   X   GRANT
58  KEY         (a0aba7857f1b)  72057594040549300   S   WAIT
58  OBJECT                      245575913           IS  GRANT
57  OBJECT                      245575913           IX  GRANT

Die Ergebnisse zeigen, dass das Abfragefenster eins (SPID 57) eine gemeinsame Sperre (S) für die DATABASE, eine beabsichtigte eXlusive-Sperre (IX) für das OBJECT, eine beabsichtigte eXlusive-Sperre (IX) für die PAGE, in die es eingefügt werden soll, und eine exklusive Sperre hat sperren Sie (X) für den KEY, den es eingefügt hat, aber noch nicht festgeschrieben hat.

Aufgrund der nicht festgeschriebenen Daten verfügt die zweite Abfrage (SPID 58) über eine gemeinsame Sperre (S) auf der Ebene DATABASE, eine beabsichtigte gemeinsame Sperre (IS) für das OBJECT und eine beabsichtigte gemeinsame Sperre (IS) auf der Seite a Shared (S) ) den SCHLÜSSEL mit dem Anforderungsstatus WARTEN abschließen.

Zusammenfassung

Die Abfrage im ersten Abfragefenster wird ohne Commit ausgeführt. Da die zweite Abfrage nur READ COMMITTEDDaten enthalten kann, wartet sie entweder, bis das Timeout auftritt oder bis die Transaktion in der ersten Abfrage festgeschrieben wurde.

Dies ist meines Erachtens das Standardverhalten von Microsoft SQL Server.

Sie sollten beachten, dass die ID für nachfolgende Lesevorgänge durch SELECT-Anweisungen in der Tat in der richtigen Reihenfolge ist, wenn die erste Anweisung COMMITs ausführt.

Wenn die erste Anweisung einen ROLLBACK ausführt, finden Sie in der Sequenz eine fehlende ID, die jedoch immer noch aufsteigend sortiert ist (vorausgesetzt, Sie haben den INDEX mit der Standard- oder ASC-Option in der ID-Spalte erstellt).

Aktualisieren:

(Offen gesagt) Ja, Sie können sich darauf verlassen, dass die Identitätsspalte ordnungsgemäß funktioniert, bis Sie auf ein Problem stoßen. Es gibt nur einen HOTFIX in Bezug auf SQL Server 2000 und die Identitätsspalte auf der Microsoft-Website.

Wenn Sie sich nicht darauf verlassen können, dass die Identitätsspalte korrekt aktualisiert wird, gibt es meiner Meinung nach mehr Hotfixes oder Patches auf der Microsoft-Website.

Wenn Sie einen Microsoft-Supportvertrag haben, können Sie jederzeit einen Beratungsfall eröffnen und zusätzliche Informationen anfordern.

John aka hot2use
quelle
1
Vielen Dank für die Analyse, aber meine Frage ist, ob es ein Zeitfenster zwischen der Erzeugung des nächsten IdentityWerts und dem Erwerb der KEY-Sperre für die Zeile gibt (in die gleichzeitige Lese- / Schreibvorgänge fallen könnten). Ich denke nicht, dass dies durch Ihre Beobachtungen als unmöglich erwiesen ist, da man die Ausführung von Abfragen und die Analyse von Sperren in diesem extrem kurzen Zeitfenster nicht stoppen kann.
Fabian Schmied
Nein, Sie können die Aussagen nicht aufhalten, aber meine (langsame) Beobachtung ist das, was auf einer schnellen / normalen Basis geschieht. Sobald eine SPID eine Sperre zum Einfügen von Daten erhält, kann die andere SPID nicht dieselbe Sperre erhalten. Die schnellere Anweisung hat den Vorteil, dass die Sperre und die ID bereits nacheinander erfasst wurden. Die nächste Anweisung erhält die nächste ID, nachdem die Sperre aufgehoben wurde.
John aka hot2use
1
Normalerweise stimmen Ihre Beobachtungen mit meinen eigenen (und auch meinen Erwartungen) überein - das ist gut zu wissen. Ich frage mich, ob es Ausnahmesituationen gibt, in denen sie sich nicht halten.
Fabian Schmied