Ако не поддържате маса за брояч, има два варианта. В рамките на транзакция първо изберете MAX(seq_id)
с една от следните таблични подсказки:
WITH(TABLOCKX, HOLDLOCK)
WITH(ROWLOCK, XLOCK, HOLDLOCK)
TABLOCKX + HOLDLOCK
е малко пресилено. Той блокира обикновените оператори за избиране, които могат да се считат за тежки въпреки че транзакцията е малка.
A ROWLOCK, XLOCK, HOLDLOCK
намек за таблица вероятно е по-добра идея (но:прочетете алтернативата с таблица за брояч по-нататък). Предимството е, че не блокира обикновените оператори за избор, т.е. когато операторите за избор не се появяват в SERIALIZABLE
транзакция или когато операторите за избор не предоставят едни и същи съвети за таблица. Използване на ROWLOCK, XLOCK, HOLDLOCK
пак ще блокира изрази за вмъкване.
Разбира се, трябва да сте сигурни, че никоя друга част от вашата програма не избира MAX(seq_id)
без тези таблични подсказки (или извън SERIALIZABLE
транзакция) и след това използвайте тази стойност за вмъкване на редове.
Имайте предвид, че в зависимост от броя на редовете, които са заключени по този начин, е възможно SQL Server да ескалира заключването до заключване на таблица. Прочетете повече за ескалацията на заключване тук .
Процедурата за вмъкване използва WITH(ROWLOCK, XLOCK, HOLDLOCK)
ще изглежда по следния начин:
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
Алтернатива и вероятно по-добра идея е да имате брояч маса и дайте тези подсказки за масата на масата за брояч. Тази таблица ще изглежда по следния начин:
CREATE TABLE dbo.counter_seq(model INT PRIMARY KEY, seq_id INT);
След това бихте променили процедурата за вмъкване, както следва:
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
Предимството е, че се използват по-малко заключвания на редове (т.е. по едно на модел в dbo.counter_seq
), а ескалацията на заключване не може да заключи целия dbo.table_seq
таблица, като по този начин блокира избраните оператори.
Можете да тествате всичко това и сами да видите ефектите, като поставите WAITFOR DELAY '00:01:00'
след избиране на последователността от counter_seq
, и си играете с таблицата(ите) във втори раздел SSMS.
PS1:Използване на ROW_NUMBER() OVER (PARTITION BY model ORDER BY ID)
не е добър начин. Ако редовете са изтрити/добавени или идентификаторите са променени, последователността ще се промени (помислете за идентификатори на фактури, които никога не трябва да се променят). Също така по отношение на производителността, необходимостта да се определят номерата на редовете на всички предишни редове при извличане на един ред е лоша идея.
PS2:Никога не бих използвал външни ресурси, за да осигуря заключване, когато SQL Server вече осигурява заключване чрез нива на изолация или фини съвети за таблица.