Hvis du ikke vedligeholder et bordbord, er der to muligheder. Inden for en transaktion skal du først vælge MAX(seq_id)
med et af følgende tabeltip:
WITH(TABLOCKX, HOLDLOCK)
WITH(ROWLOCK, XLOCK, HOLDLOCK)
TABLOCKX + HOLDLOCK
er lidt overkill. Det blokerer almindelige udvalgte sætninger, som kan betragtes som tunge selvom transaktionen er lille.
En ROWLOCK, XLOCK, HOLDLOCK
bordtip er nok en bedre idé (men:læs alternativet med et bordbord længere fremme). Fordelen er, at den ikke blokerer for almindelige select-sætninger, dvs. når select-sætningerne ikke vises i en SERIALIZABLE
transaktion, eller når de udvalgte udsagn ikke giver de samme tabeltip. Brug ROWLOCK, XLOCK, HOLDLOCK
vil stadig blokere indsæt sætninger.
Du skal selvfølgelig være sikker på, at ingen andre dele af dit program vælger MAX(seq_id)
uden disse tabeltip (eller uden for en SERIALIZABLE
transaktion), og brug derefter denne værdi til at indsætte rækker.
Bemærk, at afhængigt af antallet af rækker, der er låst på denne måde, er det muligt, at SQL Server vil eskalere låsen til en tabellås. Læs mere om låseeskalering her .
Indsættelsesproceduren ved hjælp af WITH(ROWLOCK, XLOCK, HOLDLOCK)
ville se ud som følger:
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
Et alternativ og sandsynligvis en bedre idé er at have en tæller bord, og giv disse bordtips på bordbordet. Denne tabel vil se sådan ud:
CREATE TABLE dbo.counter_seq(model INT PRIMARY KEY, seq_id INT);
Du vil derefter ændre indsætningsproceduren som følger:
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
Fordelen er, at der bruges færre rækkelåse (dvs. én pr. model i dbo.counter_seq
), og låseeskalering kan ikke låse hele dbo.table_seq
tabel blokerer således udvalgte udsagn.
Du kan teste alt dette og selv se effekterne ved at placere en WAITFOR DELAY '00:01:00'
efter at have valgt sekvensen fra counter_seq
, og fifler med tabellen/tabellerne i en anden SSMS-fane.
PS1:Brug af ROW_NUMBER() OVER (PARTITION BY model ORDER BY ID)
er ikke en god måde. Hvis rækker slettes/tilføjes, eller ID'er ændres, ændres rækkefølgen (overvej faktura-id'er, der aldrig bør ændres). Også med hensyn til ydeevne at skulle bestemme rækkenumrene for alle tidligere rækker, når du henter en enkelt række, er det en dårlig idé.
PS2:Jeg ville aldrig bruge eksterne ressourcer til at levere låsning, når SQL Server allerede tilbyder låsning gennem isolationsniveauer eller finmaskede tabeltip.