ฉันมีตารางที่ใช้โดยแอปพลิเคชันรุ่นเก่าแทนIDENTITY
เขตข้อมูลในตารางอื่น ๆ
แถวในตารางแต่ละเก็บ ID ที่ใช้ล่าสุดสำหรับเขตที่มีชื่อในLastID
IDName
บางครั้ง proc ที่เก็บไว้จะได้รับ deadlock - ฉันเชื่อว่าฉันได้สร้างตัวจัดการข้อผิดพลาดที่เหมาะสม แต่ฉันสนใจที่จะดูว่าวิธีการนี้ใช้ได้ตามที่ฉันคิดหรือไม่หรือถ้าฉันเห่าต้นไม้ผิดที่นี่
ฉันค่อนข้างแน่ใจว่าควรมีวิธีการเข้าถึงตารางนี้โดยไม่มีการหยุดชะงักใด ๆ เลย
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,
);
และดัชนี nonclustered บน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
แก้ไข:
ฉันได้เพิ่มดัชนีใหม่เนื่องจากดัชนีที่มีอยู่ IX_tblIDs_Name ไม่ได้ถูกใช้โดย SP; ฉันถือว่าตัวประมวลผลแบบสอบถามใช้ดัชนีคลัสเตอร์เนื่องจากต้องการค่าที่เก็บไว้ใน 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 ดังนั้นความตั้งใจจะผนวกรหัสใหม่เข้ากับรายการมิฉะนั้นเรากำลังอัพเดทแถวที่มีอยู่ในรายการ
SERIALIZABLE
ที่นี่