IDENTITY
다양한 다른 테이블의 필드 대신 레거시 응용 프로그램에서 사용하는 테이블이 있습니다.
테이블의 각 행은에 LastID
이름이 지정된 필드에 마지막으로 사용한 ID 를 저장합니다 IDName
.
때로는 저장된 proc이 교착 상태를 겪습니다. 적절한 오류 처리기를 만들었습니다. 그러나이 방법론이 생각하는 것처럼 작동하는지 또는 여기 잘못된 트리를 짖고 있는지 확인하고 싶습니다.
교착 상태 없이이 테이블에 액세스 할 수있는 방법이 있어야한다고 확신합니다.
데이터베이스 자체는로 구성됩니다 READ_COMMITTED_SNAPSHOT = 1
.
먼저 다음은 테이블입니다.
CREATE TABLE [dbo].[tblIDs](
[IDListID] [int] NOT NULL
CONSTRAINT PK_tblIDs
PRIMARY KEY CLUSTERED
IDENTITY(1,1) ,
[IDName] [nvarchar](255) NULL,
[LastID] [int] NULL,
);
그리고 IDName
필드 의 비 클러스터형 인덱스 :
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
일부 샘플 데이터 :
INSERT INTO tblIDs (IDName, LastID)
VALUES ('SomeTestID', 1);
INSERT INTO tblIDs (IDName, LastID)
VALUES ('SomeOtherTestID', 1);
GO
테이블에 저장된 값을 업데이트하고 다음 ID를 반환하는 데 사용되는 저장 프로 시저 :
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
저장된 proc의 샘플 실행 :
EXEC GetNextID 'SomeTestID';
NewID
2
EXEC GetNextID 'SomeTestID';
NewID
3
EXEC GetNextID 'SomeOtherTestID';
NewID
2
편집하다:
SP에서 기존 인덱스 IX_tblIDs_Name을 사용하지 않기 때문에 새 인덱스를 추가했습니다. 쿼리 프로세서는 LastID에 저장된 값이 필요하기 때문에 클러스터형 인덱스를 사용한다고 가정합니다. 어쨌든이 인덱스는 실제 실행 계획에서 사용됩니다.
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);
편집 # 2 :
@AaronBertrand가 제공 한 조언을 약간 수정했습니다. 여기서 일반적인 아이디어는 불필요한 잠금을 제거하고 전체적으로 SP를보다 효율적으로 만들기 위해 명령문을 수정하는 것입니다.
코드는 아래에서 위의 코드 대신 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;
코드가 0으로이 테이블에 레코드를 추가하지 LastID
않으므로 @NewID가 1이면 의도가 목록에 새 ID를 추가한다고 가정 할 수 있습니다. 그렇지 않으면 목록의 기존 행을 업데이트합니다.
SERIALIZABLE
여기로 이관하고 있습니다.