Als u geen toonbanktafel onderhoudt, zijn er twee opties. Selecteer binnen een transactie eerst de MAX(seq_id)
met een van de volgende tabelhints:
WITH(TABLOCKX, HOLDLOCK)
WITH(ROWLOCK, XLOCK, HOLDLOCK)
TABLOCKX + HOLDLOCK
is een beetje overdreven. Het blokkeert reguliere select-statements, die als zwaar kunnen worden beschouwd ook al is de transactie klein.
A ROWLOCK, XLOCK, HOLDLOCK
tafelhint is waarschijnlijk een beter idee (maar:lees het alternatief met een tellertabel verderop). Het voordeel is dat het reguliere select-statements niet blokkeert, dat wil zeggen wanneer de select-statements niet voorkomen in een SERIALIZABLE
transactie, of wanneer de select-instructies niet dezelfde tabelhints bieden. ROWLOCK, XLOCK, HOLDLOCK
gebruiken zal nog steeds insert-statements blokkeren.
Natuurlijk moet je er zeker van zijn dat geen andere delen van je programma de MAX(seq_id)
selecteren zonder deze tabelhints (of buiten een SERIALIZABLE
transactie) en gebruik vervolgens deze waarde om rijen in te voegen.
Houd er rekening mee dat, afhankelijk van het aantal rijen dat op deze manier is vergrendeld, het mogelijk is dat SQL Server de vergrendeling escaleert naar een tabelvergrendeling. Lees hier .
De invoegprocedure met behulp van WITH(ROWLOCK, XLOCK, HOLDLOCK)
ziet er als volgt uit:
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
Een alternatief en waarschijnlijk een beter idee is om een teller te hebben tafel, en geef deze tafelhints op de aanrechttafel. Deze tabel ziet er als volgt uit:
CREATE TABLE dbo.counter_seq(model INT PRIMARY KEY, seq_id INT);
U zou dan de invoegprocedure als volgt wijzigen:
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
Het voordeel is dat er minder rijvergrendelingen worden gebruikt (dwz één per model in dbo.counter_seq
), en lock escalatie kan de hele dbo.table_seq
niet vergrendelen tabel waardoor select-statements worden geblokkeerd.
U kunt dit allemaal testen en zelf de effecten zien door een WAITFOR DELAY '00:01:00'
te plaatsen na het selecteren van de reeks uit counter_seq
, en spelen met de tafel(s) in een tweede SSMS-tabblad.
PS1:ROW_NUMBER() OVER (PARTITION BY model ORDER BY ID)
gebruiken is geen goede manier. Als rijen worden verwijderd/toegevoegd, of ID's worden gewijzigd, verandert de volgorde (denk aan factuur-ID's die nooit mogen veranderen). Ook qua prestaties is het een slecht idee om de rijnummers van alle voorgaande rijen te bepalen bij het ophalen van een enkele rij.
PS2:ik zou nooit externe bronnen gebruiken om vergrendeling te bieden, wanneer SQL Server al vergrendeling biedt via isolatieniveaus of fijnmazige tabelhints.