Sqlserver
 sql >> Datenbank >  >> RDS >> Sqlserver

So erhalten Sie die nächste Zahl in einer Sequenz

Wenn Sie keine Zählertabelle pflegen, gibt es zwei Möglichkeiten. Wählen Sie innerhalb einer Transaktion zuerst den MAX(seq_id) aus mit einem der folgenden Tabellenhinweise:

  1. WITH(TABLOCKX, HOLDLOCK)
  2. WITH(ROWLOCK, XLOCK, HOLDLOCK)

TABLOCKX + HOLDLOCK ist etwas übertrieben. Es blockiert reguläre Select-Anweisungen, die als schwer angesehen werden können obwohl die Transaktion klein ist.

Ein ROWLOCK, XLOCK, HOLDLOCK Tabellenhinweis ist wahrscheinlich eine bessere Idee (aber:lesen Sie die Alternative mit einer Zählertabelle weiter unten). Der Vorteil ist, dass es keine regulären Select-Anweisungen blockiert, dh wenn die Select-Anweisungen nicht in einem SERIALIZABLE erscheinen Transaktion oder wenn die Select-Anweisungen nicht die gleichen Tabellenhinweise liefern. Mit ROWLOCK, XLOCK, HOLDLOCK blockiert immer noch Einfügeanweisungen.

Natürlich müssen Sie sicher sein, dass keine anderen Teile Ihres Programms MAX(seq_id) auswählen ohne diese Tabellenhinweise (oder außerhalb eines SERIALIZABLE Transaktion) und verwenden Sie dann diesen Wert, um Zeilen einzufügen.

Beachten Sie, dass abhängig von der Anzahl der Zeilen, die auf diese Weise gesperrt werden, es möglich ist, dass SQL Server die Sperre zu einer Tabellensperre eskaliert. Weitere Informationen zur Sperreneskalation finden Sie hier .

Die Einfügeprozedur mit WITH(ROWLOCK, XLOCK, HOLDLOCK) würde wie folgt aussehen:

DECLARE @target_model INT=3;
DECLARE @part VARCHAR(128)='Spine';
BEGIN TRY
    BEGIN TRANSACTION;
    DECLARE @max_seq INT=(SELECT MAX(seq) FROM dbo.table_seq WITH(ROWLOCK,XLOCK,HOLDLOCK) WHERE [email protected]_model);
    IF @max_seq IS NULL SET @max_seq=0;
    INSERT INTO dbo.table_seq(part,seq,model)VALUES(@part,@max_seq+1,@target_model);
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    ROLLBACK TRANSACTION;
END CATCH

Eine Alternative und wahrscheinlich eine bessere Idee ist es, einen Zähler zu haben Tisch und stellen Sie diese Tischhinweise auf dem Tresentisch zur Verfügung. Diese Tabelle würde wie folgt aussehen:

CREATE TABLE dbo.counter_seq(model INT PRIMARY KEY, seq_id INT);

Sie würden dann die Einfügeprozedur wie folgt ändern:

DECLARE @target_model INT=3;
DECLARE @part VARCHAR(128)='Spine';
BEGIN TRY
    BEGIN TRANSACTION;
    DECLARE @new_seq INT=(SELECT seq FROM dbo.counter_seq WITH(ROWLOCK,XLOCK,HOLDLOCK) WHERE [email protected]_model);
    IF @new_seq IS NULL 
        BEGIN SET @new_seq=1; INSERT INTO dbo.counter_seq(model,seq)VALUES(@target_model,@new_seq); END
    ELSE
        BEGIN SET @new_seq+=1; UPDATE dbo.counter_seq SET [email protected]_seq WHERE [email protected]_model; END
    INSERT INTO dbo.table_seq(part,seq,model)VALUES(@part,@new_seq,@target_model);
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    ROLLBACK TRANSACTION;
END CATCH

Der Vorteil ist, dass weniger Zeilensperren verwendet werden (dh eine pro Modell in dbo.counter_seq ), und die Sperreneskalation kann nicht die gesamte dbo.table_seq sperren Tabelle, wodurch ausgewählte Anweisungen blockiert werden.

Sie können dies alles testen und die Auswirkungen selbst sehen, indem Sie ein WAITFOR DELAY '00:01:00' setzen nach Auswahl der Sequenz aus counter_seq , und mit den Tabellen in einer zweiten SSMS-Registerkarte herumspielen.

PS1:Verwendung von ROW_NUMBER() OVER (PARTITION BY model ORDER BY ID) ist kein guter Weg. Wenn Zeilen gelöscht/hinzugefügt oder IDs geändert werden, würde sich die Reihenfolge ändern (beachten Sie Rechnungs-IDs, die sich nie ändern sollten). Auch im Hinblick auf die Performance ist es eine schlechte Idee, beim Abrufen einer einzelnen Zeile die Zeilennummern aller vorherigen Zeilen ermitteln zu müssen.

PS2:Ich würde niemals externe Ressourcen verwenden, um Sperren bereitzustellen, wenn SQL Server bereits Sperren durch Isolationsstufen oder detaillierte Tabellenhinweise bereitstellt.