Jeśli nie prowadzisz tabeli liczników, masz dwie możliwości. W ramach transakcji najpierw wybierz MAX(seq_id)
z jedną z poniższych wskazówek dotyczących tabeli:
WITH(TABLOCKX, HOLDLOCK)
WITH(ROWLOCK, XLOCK, HOLDLOCK)
TABLOCKX + HOLDLOCK
to trochę przesada. Blokuje zwykłe instrukcje select, które można uznać za ciężkie nawet jeśli transakcja jest niewielka.
ROWLOCK, XLOCK, HOLDLOCK
wskazówka tabeli jest prawdopodobnie lepszym pomysłem (ale:przeczytaj alternatywę z tabelą licznika dalej). Zaletą jest to, że nie blokuje zwykłych instrukcji select, tj. gdy instrukcje select nie pojawiają się w SERIALIZABLE
transakcji lub gdy instrukcje select nie zawierają tych samych wskazówek dotyczących tabeli. Używanie ROWLOCK, XLOCK, HOLDLOCK
nadal będzie blokować instrukcje wstawiania.
Oczywiście musisz mieć pewność, że żadna inna część twojego programu nie wybiera MAX(seq_id)
bez tych wskazówek tabeli (lub poza SERIALIZABLE
transakcji), a następnie użyj tej wartości do wstawienia wierszy.
Należy zauważyć, że w zależności od liczby wierszy, które są w ten sposób zablokowane, możliwe jest, że SQL Server przekształci blokadę w blokadę tabeli. Przeczytaj więcej o eskalacji blokady tutaj .
Procedura wstawiania przy użyciu WITH(ROWLOCK, XLOCK, HOLDLOCK)
wyglądałby następująco:
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
Alternatywą i prawdopodobnie lepszym pomysłem jest posiadanie licznika tabeli i podaj te wskazówki na stole licznika. Ta tabela wyglądałaby następująco:
CREATE TABLE dbo.counter_seq(model INT PRIMARY KEY, seq_id INT);
Następnie zmienisz procedurę wstawiania w następujący sposób:
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
Zaletą jest to, że używa się mniej blokad wierszy (tj. jedna na model w dbo.counter_seq
), a eskalacja blokady nie może zablokować całego pliku dbo.table_seq
tabeli, blokując w ten sposób polecenia select.
Możesz to wszystko przetestować i samemu zobaczyć efekty, umieszczając WAITFOR DELAY '00:01:00'
po wybraniu sekwencji z counter_seq
i majstrowanie przy tabelach w drugiej zakładce SSMS.
PS1:Używanie ROW_NUMBER() OVER (PARTITION BY model ORDER BY ID)
nie jest dobrym sposobem. Jeśli wiersze zostaną usunięte/dodane lub zmienione zostaną identyfikatory, kolejność ulegnie zmianie (należy wziąć pod uwagę identyfikatory faktury, które nigdy nie powinny się zmieniać). Również pod względem wydajności konieczność określania numerów wszystkich poprzednich wierszy podczas pobierania pojedynczego wiersza jest złym pomysłem.
PS2:Nigdy nie używałbym zewnętrznych zasobów do blokowania, gdy SQL Server już zapewnia blokowanie poprzez poziomy izolacji lub szczegółowe wskazówki dotyczące tabel.