Tôi có một bảng được sử dụng bởi một ứng dụng cũ để thay thế cho IDENTITY
các trường trong các bảng khác nhau.
Mỗi hàng trong bảng lưu trữ ID được sử dụng cuối cùng LastID
cho trường có tên IDName
.
Đôi khi, Proc được lưu trữ bị bế tắc - Tôi tin rằng tôi đã xây dựng một trình xử lý lỗi thích hợp; tuy nhiên tôi quan tâm xem liệu phương pháp này có hiệu quả như tôi nghĩ hay không, nếu tôi sủa sai cây ở đây.
Tôi khá chắc chắn nên có một cách để truy cập vào bảng này mà không có bất kỳ sự bế tắc nào cả.
Cơ sở dữ liệu được cấu hình với READ_COMMITTED_SNAPSHOT = 1
.
Đầu tiên, đây là bảng:
CREATE TABLE [dbo].[tblIDs](
[IDListID] [int] NOT NULL
CONSTRAINT PK_tblIDs
PRIMARY KEY CLUSTERED
IDENTITY(1,1) ,
[IDName] [nvarchar](255) NULL,
[LastID] [int] NULL,
);
Và chỉ mục không bao gồm trên IDName
trường:
CREATE NONCLUSTERED INDEX [IX_tblIDs_IDName]
ON [dbo].[tblIDs]
(
[IDName] ASC
)
WITH (
PAD_INDEX = OFF
, STATISTICS_NORECOMPUTE = OFF
, SORT_IN_TEMPDB = OFF
, DROP_EXISTING = OFF
, ONLINE = OFF
, ALLOW_ROW_LOCKS = ON
, ALLOW_PAGE_LOCKS = ON
, FILLFACTOR = 80
);
GO
Một số dữ liệu mẫu:
INSERT INTO tblIDs (IDName, LastID)
VALUES ('SomeTestID', 1);
INSERT INTO tblIDs (IDName, LastID)
VALUES ('SomeOtherTestID', 1);
GO
Quy trình được lưu trữ được sử dụng để cập nhật các giá trị được lưu trữ trong bảng và trả về ID tiếp theo:
CREATE PROCEDURE [dbo].[GetNextID](
@IDName nvarchar(255)
)
AS
BEGIN
/*
Description: Increments and returns the LastID value from tblIDs
for a given IDName
Author: Max Vernon
Date: 2012-07-19
*/
DECLARE @Retry int;
DECLARE @EN int, @ES int, @ET int;
SET @Retry = 5;
DECLARE @NewID int;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SET NOCOUNT ON;
WHILE @Retry > 0
BEGIN
BEGIN TRY
BEGIN TRANSACTION;
SET @NewID = COALESCE((SELECT LastID
FROM tblIDs
WHERE IDName = @IDName),0)+1;
IF (SELECT COUNT(IDName)
FROM tblIDs
WHERE IDName = @IDName) = 0
INSERT INTO tblIDs (IDName, LastID)
VALUES (@IDName, @NewID)
ELSE
UPDATE tblIDs
SET LastID = @NewID
WHERE IDName = @IDName;
COMMIT TRANSACTION;
SET @Retry = -2; /* no need to retry since the operation completed */
END TRY
BEGIN CATCH
IF (ERROR_NUMBER() = 1205) /* DEADLOCK */
SET @Retry = @Retry - 1;
ELSE
BEGIN
SET @Retry = -1;
SET @EN = ERROR_NUMBER();
SET @ES = ERROR_SEVERITY();
SET @ET = ERROR_STATE()
RAISERROR (@EN,@ES,@ET);
END
ROLLBACK TRANSACTION;
END CATCH
END
IF @Retry = 0 /* must have deadlock'd 5 times. */
BEGIN
SET @EN = 1205;
SET @ES = 13;
SET @ET = 1
RAISERROR (@EN,@ES,@ET);
END
ELSE
SELECT @NewID AS NewID;
END
GO
Mẫu thực thi của Proc được lưu trữ:
EXEC GetNextID 'SomeTestID';
NewID
2
EXEC GetNextID 'SomeTestID';
NewID
3
EXEC GetNextID 'SomeOtherTestID';
NewID
2
CHỈNH SỬA:
Tôi đã thêm một chỉ mục mới, vì chỉ mục hiện tại IX_tblIDs_Name không được SP sử dụng; Tôi giả sử bộ xử lý truy vấn đang sử dụng chỉ mục được nhóm vì nó cần giá trị được lưu trữ trong LastID. Dù sao, chỉ số này IS được sử dụng bởi kế hoạch thực hiện thực tế:
CREATE NONCLUSTERED INDEX IX_tblIDs_IDName_LastID
ON dbo.tblIDs
(
IDName ASC
)
INCLUDE
(
LastID
)
WITH (FILLFACTOR = 100
, ONLINE=ON
, ALLOW_ROW_LOCKS = ON
, ALLOW_PAGE_LOCKS = ON);
EDIT # 2:
Tôi đã thực hiện lời khuyên mà @AaronBertrand đã đưa ra và sửa đổi nó một chút. Ý tưởng chung ở đây là tinh chỉnh tuyên bố để loại bỏ việc khóa không cần thiết, và tổng thể để làm cho SP hiệu quả hơn.
Đoạn code dưới đây thay thế mã trên từ BEGIN TRANSACTION
để END TRANSACTION
:
BEGIN TRANSACTION;
SET @NewID = COALESCE((SELECT LastID
FROM dbo.tblIDs
WHERE IDName = @IDName), 0) + 1;
IF @NewID = 1
INSERT INTO tblIDs (IDName, LastID)
VALUES (@IDName, @NewID);
ELSE
UPDATE dbo.tblIDs
SET LastID = @NewID
WHERE IDName = @IDName;
COMMIT TRANSACTION;
Vì mã của chúng tôi không bao giờ thêm bản ghi vào bảng này với 0 trong nên LastID
chúng tôi có thể đưa ra giả định rằng nếu @NewID là 1 thì ý định sẽ thêm ID mới vào danh sách, ngoài ra chúng tôi đang cập nhật một hàng hiện có trong danh sách.
SERIALIZABLE
đây.