Om du inte underhåller ett bänkbord finns det två alternativ. Inom en transaktion väljer du först MAX(seq_id)
med ett av följande tabelltips:
WITH(TABLOCKX, HOLDLOCK)
WITH(ROWLOCK, XLOCK, HOLDLOCK)
TABLOCKX + HOLDLOCK
är lite överdrivet. Det blockerar vanliga urvalssatser, som kan anses vara tunga även om transaktionen är liten.
En ROWLOCK, XLOCK, HOLDLOCK
bordstips är förmodligen en bättre idé (men:läs alternativet med ett motbord längre fram). Fördelen är att den inte blockerar vanliga select-satser, dvs när select-satserna inte visas i en SERIALIZABLE
transaktion, eller när urvalssatserna inte ger samma tabelltips. Använd ROWLOCK, XLOCK, HOLDLOCK
kommer fortfarande att blockera infoga uttalanden.
Naturligtvis måste du vara säker på att inga andra delar av ditt program väljer MAX(seq_id)
utan dessa tabelltips (eller utanför en SERIALIZABLE
transaktion) och använd sedan detta värde för att infoga rader.
Observera att beroende på antalet rader som är låsta på detta sätt, är det möjligt att SQL Server kommer att eskalera låset till ett tabelllås. Läs mer om låseskalering här .
Infogningsproceduren med WITH(ROWLOCK, XLOCK, HOLDLOCK)
skulle se ut som följer:
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
Ett alternativ och förmodligen en bättre idé är att ha en disk tabell och ge dessa tabelltips på bänkbordet. Den här tabellen skulle se ut så här:
CREATE TABLE dbo.counter_seq(model INT PRIMARY KEY, seq_id INT);
Du skulle sedan ändra insättningsproceduren enligt följande:
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
Fördelen är att färre radlås används (dvs ett per modell i dbo.counter_seq
), och låseskalering kan inte låsa hela dbo.table_seq
tabell blockerar alltså valda uttalanden.
Du kan testa allt detta och se effekterna själv genom att placera en WAITFOR DELAY '00:01:00'
efter att ha valt sekvensen från counter_seq
, och pilla med tabellen/tabellerna i en andra SSMS-flik.
PS1:Använder ROW_NUMBER() OVER (PARTITION BY model ORDER BY ID)
är inte ett bra sätt. Om rader raderas/läggs till, eller ID ändras, kommer sekvensen att ändras (tänk på faktura-ID som aldrig bör ändras). Även när det gäller prestanda är det en dålig idé att behöva bestämma radnumren för alla tidigare rader när man hämtar en enda rad.
PS2:Jag skulle aldrig använda externa resurser för att tillhandahålla låsning, när SQL Server redan tillhandahåller låsning genom isoleringsnivåer eller finkorniga tabelltips.