Eski bir uygulama tarafından IDENTITY
diğer çeşitli tablolardaki alanların yerine kullanılan bir tablo var .
Tablodaki her satır, içinde LastID
adlandırılan alan için en son kullanılan kimliği saklar IDName
.
Zaman zaman saklanan işlem kilitlenmeye başlar - Ben uygun bir hata işleyicisi kurduğuma inanıyorum; Bununla birlikte, bu metodolojinin düşündüğüm gibi çalışıp çalışmadığını veya burada yanlış ağaca ağaç kabuğu alıp almadığımı görmek istiyorum.
Bu tabloya hiçbir kilitlenme olmadan erişmenin bir yolu olması gerektiğinden kesinlikle eminim.
Veritabanının kendisi ile yapılandırılmıştır READ_COMMITTED_SNAPSHOT = 1
.
İlk olarak, işte tablo:
CREATE TABLE [dbo].[tblIDs](
[IDListID] [int] NOT NULL
CONSTRAINT PK_tblIDs
PRIMARY KEY CLUSTERED
IDENTITY(1,1) ,
[IDName] [nvarchar](255) NULL,
[LastID] [int] NULL,
);
Ve IDName
sahadaki kümelenmemiş indeks :
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
Bazı örnek veriler:
INSERT INTO tblIDs (IDName, LastID)
VALUES ('SomeTestID', 1);
INSERT INTO tblIDs (IDName, LastID)
VALUES ('SomeOtherTestID', 1);
GO
Tabloda depolanan değerleri güncellemek ve bir sonraki kimliği döndürmek için kullanılan saklı yordam:
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
Saklanan işlemin örnek uygulamaları:
EXEC GetNextID 'SomeTestID';
NewID
2
EXEC GetNextID 'SomeTestID';
NewID
3
EXEC GetNextID 'SomeOtherTestID';
NewID
2
DÜZENLE:
Yeni bir dizin ekledim, çünkü mevcut dizin IX_tblIDs_Name SP tarafından kullanılmıyor; Sorgu işlemcisinin LastID'de depolanan değere ihtiyacı olduğu için kümelenmiş dizini kullandığını farz ediyorum. Her neyse, bu endeks gerçek uygulama planı tarafından kullanılıyor:
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 DÜZENLEME:
@AaronBertrand’ın verdiği ve hafifçe değiştirdiği tavsiyesini aldım. Buradaki genel fikir, gereksiz kilitlemeyi ortadan kaldırmak için yapılan ifadeyi düzeltmek ve genel olarak SP'yi daha verimli kılmaktır.
Aşağıdaki kod adlı Yukarıdaki kodu yerine geçer BEGIN TRANSACTION
için 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;
LastID
Kodumuz hiçbir zaman bu tabloya 0 ile bir kayıt eklemediğinden, @NewID 1 ise o zaman listeye yeni bir kimlik eklemek niyetindeyiz, aksi halde listedeki mevcut bir satırı güncellediğimizi varsayabiliriz.
SERIALIZABLE
buraya tırmanıyorsun .