Nếu bạn không duy trì một bảng truy cập, có hai lựa chọn. Trong một giao dịch, trước tiên hãy chọn MAX(seq_id)
với một trong các gợi ý bảng sau:
-
WITH(TABLOCKX, HOLDLOCK)
-
WITH(ROWLOCK, XLOCK, HOLDLOCK)
TABLOCKX + HOLDLOCK
là một chút quá mức cần thiết. Nó chặn các câu lệnh chọn thông thường, có thể được coi là nặng mặc dù giao dịch nhỏ.
A ROWLOCK, XLOCK, HOLDLOCK
gợi ý về bảng có lẽ là một ý tưởng tốt hơn (nhưng:hãy đọc thêm phương án thay thế với bảng đếm trên). Ưu điểm là nó không chặn các câu lệnh select thông thường, tức là khi các câu lệnh select không xuất hiện trong SERIALIZABLE
giao dịch, hoặc khi các câu lệnh chọn không cung cấp các gợi ý bảng giống nhau. Sử dụng ROWLOCK, XLOCK, HOLDLOCK
sẽ vẫn chặn các câu lệnh chèn.
Tất nhiên, bạn cần đảm bảo rằng không có phần nào khác trong chương trình của bạn chọn MAX(seq_id)
không có các gợi ý bảng này (hoặc bên ngoài SERIALIZABLE
giao dịch) và sau đó sử dụng giá trị này để chèn các hàng.
Lưu ý rằng tùy thuộc vào số lượng hàng bị khóa theo cách này, có thể SQL Server sẽ chuyển khóa thành khóa bảng. Đọc thêm về báo cáo khóa tại đây .
Quy trình chèn bằng WITH(ROWLOCK, XLOCK, HOLDLOCK)
sẽ trông như sau:
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
Một giải pháp thay thế và có lẽ là một ý tưởng tốt hơn là có một bộ đếm bảng, và cung cấp các gợi ý bảng này trên bảng truy cập. Bảng này sẽ giống như sau:
CREATE TABLE dbo.counter_seq(model INT PRIMARY KEY, seq_id INT);
Sau đó, bạn sẽ thay đổi quy trình chèn như sau:
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
Ưu điểm là sử dụng ít khóa hàng hơn (tức là một khóa cho mỗi kiểu máy trong dbo.counter_seq
) và khóa leo thang không thể khóa toàn bộ dbo.table_seq
bảng do đó chặn các câu lệnh chọn.
Bạn có thể kiểm tra tất cả những điều này và tự xem các hiệu ứng bằng cách đặt WAITFOR DELAY '00:01:00'
sau khi chọn trình tự từ counter_seq
và tìm hiểu (các) bảng trong tab SSMS thứ hai.
PS1:Sử dụng ROW_NUMBER() OVER (PARTITION BY model ORDER BY ID)
không phải là một cách tốt. Nếu hàng bị xóa / thêm hoặc ID thay đổi, trình tự sẽ thay đổi (hãy xem xét id hóa đơn không bao giờ thay đổi). Ngoài ra, về mặt hiệu suất, việc xác định số hàng của tất cả các hàng trước đó khi truy xuất một hàng là một ý tưởng tồi.
PS2:Tôi sẽ không bao giờ sử dụng các tài nguyên bên ngoài để cung cấp khóa, khi SQL Server đã cung cấp khóa thông qua các mức cách ly hoặc các gợi ý bảng chi tiết.