Saya memiliki tabel yang digunakan oleh aplikasi lawas sebagai pengganti IDENTITY
bidang di berbagai tabel lainnya.
Setiap baris dalam tabel menyimpan ID yang terakhir digunakan LastID
untuk bidang yang disebutkan dalam IDName
.
Terkadang proc yang disimpan menemui jalan buntu - Saya yakin saya telah membangun penangan kesalahan yang sesuai; namun saya tertarik untuk melihat apakah metodologi ini berfungsi seperti yang saya kira, atau jika saya menggonggong pohon yang salah di sini.
Saya cukup yakin harus ada cara untuk mengakses tabel ini tanpa ada deadlock sama sekali.
Basis data itu sendiri dikonfigurasi dengan READ_COMMITTED_SNAPSHOT = 1
.
Pertama, ini adalah tabelnya:
CREATE TABLE [dbo].[tblIDs](
[IDListID] [int] NOT NULL
CONSTRAINT PK_tblIDs
PRIMARY KEY CLUSTERED
IDENTITY(1,1) ,
[IDName] [nvarchar](255) NULL,
[LastID] [int] NULL,
);
Dan indeks nonclustered di IDName
lapangan:
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
Beberapa data sampel:
INSERT INTO tblIDs (IDName, LastID)
VALUES ('SomeTestID', 1);
INSERT INTO tblIDs (IDName, LastID)
VALUES ('SomeOtherTestID', 1);
GO
Prosedur tersimpan digunakan untuk memperbarui nilai-nilai yang disimpan dalam tabel, dan mengembalikan ID berikutnya:
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
Contoh eksekusi dari proc yang disimpan:
EXEC GetNextID 'SomeTestID';
NewID
2
EXEC GetNextID 'SomeTestID';
NewID
3
EXEC GetNextID 'SomeOtherTestID';
NewID
2
EDIT:
Saya telah menambahkan indeks baru, karena indeks yang ada IX_tblIDs_Name tidak digunakan oleh SP; Saya berasumsi prosesor permintaan menggunakan indeks berkerumun karena membutuhkan nilai yang disimpan dalam LastID. Bagaimanapun, indeks ini digunakan oleh rencana eksekusi aktual:
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:
Saya telah menerima saran yang diberikan @AaronBertrand dan memodifikasinya sedikit. Gagasan umum di sini adalah untuk memperbaiki pernyataan untuk menghilangkan penguncian yang tidak perlu, dan secara keseluruhan untuk membuat SP lebih efisien.
Kode di bawah ini menggantikan kode di atas dari BEGIN TRANSACTION
ke 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;
Karena kode kita tidak pernah menambahkan catatan ke tabel ini dengan 0 di LastID
kita dapat membuat asumsi bahwa jika @NewID adalah 1 maka maksudnya adalah menambahkan ID baru ke daftar, kalau tidak kita memperbarui baris yang ada dalam daftar.
SERIALIZABLE
sini.