การจัดการการเข้าถึงตารางคีย์พร้อมกันโดยไม่มี deadlocks ใน SQL Server


32

ฉันมีตารางที่ใช้โดยแอปพลิเคชันรุ่นเก่าแทนIDENTITYเขตข้อมูลในตารางอื่น ๆ

แถวในตารางแต่ละเก็บ ID ที่ใช้ล่าสุดสำหรับเขตที่มีชื่อในLastIDIDName

บางครั้ง 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 ดังนั้นความตั้งใจจะผนวกรหัสใหม่เข้ากับรายการมิฉะนั้นเรากำลังอัพเดทแถวที่มีอยู่ในรายการ


วิธีที่คุณกำหนดค่าฐานข้อมูลเพื่อรองรับ RCSI นั้นไม่เกี่ยวข้อง คุณตั้งใจจะมาSERIALIZABLEที่นี่
แอรอนเบอร์ทรานด์ด์

ใช่ฉันแค่ต้องการเพิ่มข้อมูลที่เกี่ยวข้องทั้งหมด ฉันดีใจที่คุณยืนยันว่าไม่เกี่ยวข้อง!
Max Vernon

มันง่ายมากที่จะทำให้ sp_getapplock กลายเป็นเหยื่อการหยุดชะงัก แต่ไม่ใช่ถ้าคุณเริ่มทรานแซคชันให้โทร sp_getapplock หนึ่งครั้งเพื่อรับการล็อคแบบเอกสิทธิ์
AK

1
IDName มีลักษณะเฉพาะหรือไม่? จากนั้นแนะนำอีกครั้ง "สร้างดัชนี nonclustered ที่ไม่ซ้ำกัน " อย่างไรก็ตามหากคุณต้องการค่า Null ดัชนีก็จะต้องถูกกรองด้วย
crokusek

คำตอบ:


15

ก่อนอื่นฉันจะหลีกเลี่ยงการไปกลับฐานข้อมูลทุกค่า ตัวอย่างเช่นหากแอปพลิเคชันของคุณรู้ว่ามันต้องการ 20 ID ใหม่อย่าทำการเดินทางไปกลับ 20 ครั้ง ทำการเรียกขั้นตอนที่เก็บไว้เพียงครั้งเดียวและเพิ่มจำนวนตัวนับ 20 นอกจากนี้อาจเป็นการดีกว่าที่จะแบ่งตารางของคุณออกเป็นหลาย ๆ รายการ

เป็นไปได้ที่จะหลีกเลี่ยงการหยุดชะงักโดยสิ้นเชิง ฉันไม่มีการหยุดชะงักในระบบของฉัน มีหลายวิธีที่จะทำให้สำเร็จ ฉันจะแสดงว่าฉันจะใช้ sp_getapplock เพื่อกำจัดการหยุดชะงักได้อย่างไร ฉันไม่รู้ว่าจะใช้งานได้กับคุณหรือไม่เพราะ SQL Server เป็นแหล่งข้อมูลปิดดังนั้นฉันจึงไม่สามารถเห็นซอร์สโค้ดได้และฉันไม่ทราบว่าได้ทำการทดสอบกรณีที่เป็นไปได้ทั้งหมดหรือไม่

ต่อไปนี้จะอธิบายสิ่งที่ใช้ได้กับฉัน YMMV

ก่อนอื่นให้เราเริ่มจากสถานการณ์ที่เราได้รับการหยุดชะงักจำนวนมากอยู่เสมอ ประการที่สองเราจะใช้ sp_getapplock กำจัดพวกเขา จุดที่สำคัญที่สุดคือการทดสอบความเครียดของคุณ วิธีการแก้ปัญหาของคุณอาจแตกต่างกัน แต่คุณต้องแสดงให้เห็นพร้อมกันสูงในขณะที่ฉันจะแสดงในภายหลัง

ข้อกำหนดเบื้องต้น

ให้เราตั้งค่าตารางที่มีข้อมูลการทดสอบ:

CREATE TABLE dbo.Numbers(n INT NOT NULL PRIMARY KEY); 
GO 

INSERT INTO dbo.Numbers 
    ( n ) 
        VALUES  ( 1 ); 
GO 
DECLARE @i INT; 
    SET @i=0; 
WHILE @i<21  
    BEGIN 
    INSERT INTO dbo.Numbers 
        ( n ) 
        SELECT n + POWER(2, @i) 
        FROM dbo.Numbers; 
    SET @i = @i + 1; 
    END;  
GO

SELECT n AS ID, n AS Key1, n AS Key2, 0 AS Counter1, 0 AS Counter2
INTO dbo.DeadlockTest FROM dbo.Numbers
GO

ALTER TABLE dbo.DeadlockTest ADD CONSTRAINT PK_DeadlockTest PRIMARY KEY(ID);
GO

CREATE INDEX DeadlockTestKey1 ON dbo.DeadlockTest(Key1);
GO

CREATE INDEX DeadlockTestKey2 ON dbo.DeadlockTest(Key2);
GO

สองขั้นตอนต่อไปนี้มีแนวโน้มที่จะยอมรับในการหยุดชะงัก:

CREATE PROCEDURE dbo.UpdateCounter1 @Key1 INT
AS
SET NOCOUNT ON ;
SET XACT_ABORT ON;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN TRANSACTION ;
UPDATE dbo.DeadlockTest SET Counter1=Counter1+1 WHERE Key1=@Key1;
SET @Key1=@Key1-10000;
UPDATE dbo.DeadlockTest SET Counter1=Counter1+1 WHERE Key1=@Key1;
COMMIT;
GO

CREATE PROCEDURE dbo.UpdateCounter2 @Key2 INT
AS
SET NOCOUNT ON ;
SET XACT_ABORT ON;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN TRANSACTION ;
SET @Key2=@Key2-10000;
UPDATE dbo.DeadlockTest SET Counter2=Counter2+1 WHERE Key2=@Key2;
SET @Key2=@Key2+10000;
UPDATE dbo.DeadlockTest SET Counter2=Counter2+1 WHERE Key2=@Key2;
COMMIT;
GO

สร้าง deadlocks ใหม่

ลูปต่อไปนี้ควรทำซ้ำการหยุดชะงักมากกว่า 20 ครั้งทุกครั้งที่คุณเรียกใช้ หากคุณได้รับน้อยกว่า 20 ให้เพิ่มจำนวนการวนซ้ำ

ในหนึ่งแท็บให้เรียกใช้สิ่งนี้

DECLARE @i INT, @DeadlockCount INT;
SELECT @i=0, @DeadlockCount=0;

WHILE @i<5000 BEGIN ;
  BEGIN TRY 
    EXEC dbo.UpdateCounter1 @Key1=123456;
  END TRY
  BEGIN CATCH
    SET @DeadlockCount = @DeadlockCount + 1;
    ROLLBACK;
  END CATCH ;
  SET @i = @i + 1;
END;
SELECT 'Deadlocks caught: ', @DeadlockCount ;

ในแท็บอื่นให้เรียกใช้สคริปต์นี้

DECLARE @i INT, @DeadlockCount INT;
SELECT @i=0, @DeadlockCount=0;

WHILE @i<5000 BEGIN ;
  BEGIN TRY 
    EXEC dbo.UpdateCounter2 @Key2=123456;
  END TRY
  BEGIN CATCH
    SET @DeadlockCount = @DeadlockCount + 1;
    ROLLBACK;
  END CATCH ;
  SET @i = @i + 1;
END;
SELECT 'Deadlocks caught: ', @DeadlockCount ;

ตรวจสอบให้แน่ใจว่าคุณเริ่มต้นทั้งสองภายในไม่กี่วินาที

การใช้ sp_getapplock เพื่อกำจัดการหยุดชะงัก

เปลี่ยนทั้งสองโพรซีเดอร์รันลูปอีกครั้งและดูว่าคุณไม่มีการหยุดชะงัก:

ALTER PROCEDURE dbo.UpdateCounter1 @Key1 INT
AS
SET NOCOUNT ON ;
SET XACT_ABORT ON;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN TRANSACTION ;
EXEC sp_getapplock @Resource='DeadlockTest', @LockMode='Exclusive';
UPDATE dbo.DeadlockTest SET Counter1=Counter1+1 WHERE Key1=@Key1;
SET @Key1=@Key1-10000;
UPDATE dbo.DeadlockTest SET Counter1=Counter1+1 WHERE Key1=@Key1;
COMMIT;
GO

ALTER PROCEDURE dbo.UpdateCounter2 @Key2 INT
AS
SET NOCOUNT ON ;
SET XACT_ABORT ON;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN TRANSACTION ;
EXEC sp_getapplock @Resource='DeadlockTest', @LockMode='Exclusive';
SET @Key2=@Key2-10000;
UPDATE dbo.DeadlockTest SET Counter2=Counter2+1 WHERE Key2=@Key2;
SET @Key2=@Key2+10000;
UPDATE dbo.DeadlockTest SET Counter2=Counter2+1 WHERE Key2=@Key2;
COMMIT;
GO

ใช้ตารางที่มีหนึ่งแถวเพื่อกำจัดการหยุดชะงัก

แทนที่จะเรียกใช้ sp_getapplock เราสามารถแก้ไขตารางต่อไปนี้:

CREATE TABLE dbo.DeadlockTestMutex(
ID INT NOT NULL,
CONSTRAINT PK_DeadlockTestMutex PRIMARY KEY(ID),
Toggle INT NOT NULL);
GO

INSERT INTO dbo.DeadlockTestMutex(ID, Toggle)
VALUES(1,0);

เมื่อเราสร้างตารางและเติมข้อมูลแล้วเราสามารถแทนที่บรรทัดต่อไปนี้

EXEC sp_getapplock @Resource='DeadlockTest', @LockMode='Exclusive';

ด้วยวิธีนี้ทั้งสองขั้นตอน:

UPDATE dbo.DeadlockTestMutex SET Toggle = 1 - Toggle WHERE ID = 1;

คุณสามารถรันการทดสอบความเครียดอีกครั้งและดูด้วยตัวคุณเองว่าเราไม่มีการหยุดชะงัก

ข้อสรุป

ดังที่เราได้เห็น sp_getapplock สามารถใช้ในการเข้าถึงทรัพยากรอื่น ๆ เช่นนี้มันสามารถใช้เพื่อกำจัดการหยุดชะงัก

แน่นอนว่านี่อาจทำให้การแก้ไขช้าลงอย่างมาก ในการแก้ไขปัญหานั้นเราจำเป็นต้องเลือกความละเอียดที่เหมาะสมสำหรับการล็อคแบบเอกสิทธิ์เฉพาะบุคคลและหากเป็นไปได้ให้ทำงานกับชุดแทนแต่ละแถว

ก่อนที่จะใช้วิธีนี้คุณต้องทดสอบด้วยตนเองก่อน ก่อนอื่นคุณต้องแน่ใจว่าคุณได้รับ Deadlocks อย่างน้อยสองสามครั้งด้วยวิธีการดั้งเดิมของคุณ ประการที่สองคุณไม่ควรหยุดชะงักเมื่อคุณเรียกใช้สคริปต์ซ้ำอีกครั้งโดยใช้กระบวนงานที่เก็บไว้ที่ถูกปรับเปลี่ยน

โดยทั่วไปฉันไม่คิดว่าจะมีวิธีที่ดีในการตรวจสอบว่า T-SQL ของคุณปลอดภัยจากการหยุดชะงักเพียงแค่ดูหรือดูแผนการดำเนินการ IMO วิธีเดียวที่จะตรวจสอบว่ารหัสของคุณมีแนวโน้มที่จะเกิด deadlocks หรือไม่คือการเปิดเผยพร้อมกันในระดับสูง

ขอให้โชคดีกับการกำจัด deadlocks! เราไม่มีการหยุดชะงักใด ๆ ในระบบของเราเลยซึ่งเป็นสิ่งที่ยอดเยี่ยมสำหรับสมดุลชีวิตการทำงานของเรา


2
+1 as sp_getapplock เป็นเครื่องมือที่มีประโยชน์ที่ไม่เป็นที่รู้จัก ให้ 'ระเบียบรก' ที่อาจใช้เวลาในการแยกมันเป็นเคล็ดลับที่มีประโยชน์ในการทำให้กระบวนการที่หยุดชะงัก แต่ควรเป็นตัวเลือกแรกสำหรับกรณีเช่นนี้ซึ่งเข้าใจได้ง่ายและสามารถจัดการกับกลไกการล็อคมาตรฐานได้หรือไม่
Mark Storey-Smith

2
@ MarkStorey-Smith มันเป็นตัวเลือกแรกของฉันเพราะฉันได้ทำการวิจัยและทดสอบความเครียดเพียงครั้งเดียวและฉันสามารถนำมันกลับมาใช้ใหม่ได้ในทุกสถานการณ์ - การทำให้เป็นอนุกรมได้เกิดขึ้นแล้วดังนั้นทุกอย่างที่เกิดขึ้นหลังจาก sp_getapplock ด้วยกลไกการล็อคมาตรฐานฉันไม่สามารถมั่นใจได้เลยว่าการเพิ่มดัชนีหรือเพียงแค่รับแผนการดำเนินการอื่นอาจทำให้เกิดการหยุดชะงักในตำแหน่งที่ไม่เคยมีมาก่อน ถามฉันว่าฉันรู้
AK

ฉันเดาว่าฉันขาดอะไรบางอย่างที่เห็นได้ชัด แต่การใช้UPDATE dbo.DeadlockTestMutex SET Toggle = 1 - Toggle WHERE ID = 1;ป้องกันการหยุดชะงักได้อย่างไร?
เดล K

9

การใช้XLOCKคำใบ้ในSELECTแนวทางของคุณหรือสิ่งต่อไปนี้UPDATEควรเป็นอันตรายต่อการหยุดชะงักประเภทนี้:

DECLARE @Output TABLE ([NewId] INT);
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

BEGIN TRANSACTION;

UPDATE
    dbo.tblIDs WITH (XLOCK)
SET 
    LastID = LastID + 1
OUTPUT
    INSERTED.[LastId] INTO @Output
WHERE
    IDName = @IDName;

IF(@@ROWCOUNT = 1)
BEGIN
    SELECT @NewId = [NewId] FROM @Output;
END
ELSE
BEGIN
    SET @NewId = 1;

    INSERT dbo.tblIDs
        (IDName, LastID)
    VALUES
        (@IDName, @NewId);
END

SELECT [NewId] = @NewId ;

COMMIT TRANSACTION;

จะกลับมาพร้อมกับอีกสองสายพันธุ์อื่น ๆ (ถ้าไม่พ่ายแพ้!)


แม้ว่าXLOCKจะป้องกันตัวนับที่มีอยู่จากการอัปเดตจากการเชื่อมต่อที่หลากหลายคุณไม่จำเป็นต้องมีTABLOCKXเพื่อป้องกันไม่ให้การเชื่อมต่อหลายตัวเพิ่มตัวนับใหม่ที่เหมือนกันหรือไม่
เดล K

1
@DaleBurrell ไม่คุณมี PK หรือข้อ จำกัด ที่ไม่ซ้ำกันใน IDName
Mark Storey-Smith

7

Mike Defehr แสดงให้ฉันเห็นวิธีที่ยอดเยี่ยมในการทำสิ่งนี้ให้สำเร็จในแบบที่มีน้ำหนักเบามาก:

ALTER PROCEDURE [dbo].[GetNextID](
    @IDName nvarchar(255)
)
AS
BEGIN
    /*
        Description:    Increments and returns the LastID value from tblIDs for a given IDName
        Author:         Max Vernon / Mike Defehr
        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
            UPDATE dbo.tblIDs 
            SET @NewID = LastID = LastID + 1 
            WHERE IDName = @IDName;

            IF @NewID IS NULL
            BEGIN
                SET @NewID = 1;
                INSERT INTO tblIDs (IDName, LastID) VALUES (@IDName, @NewID);
            END
            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
        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 ที่เก็บไว้)

CREATE TABLE [dbo].[tblIDs]
(
    IDName nvarchar(255) NOT NULL,
    LastID int NULL,
    CONSTRAINT [PK_tblIDs] PRIMARY KEY CLUSTERED 
    (
        [IDName] ASC
    ) WITH 
    (
        PAD_INDEX = OFF
        , STATISTICS_NORECOMPUTE = OFF
        , IGNORE_DUP_KEY = OFF
        , ALLOW_ROW_LOCKS = ON
        , ALLOW_PAGE_LOCKS = ON
        , FILLFACTOR = 100
    ) 
);
GO

นี่คือแผนการดำเนินการสำหรับเวอร์ชั่นล่าสุด:

ป้อนคำอธิบายรูปภาพที่นี่

และนี่คือแผนการดำเนินการสำหรับเวอร์ชันดั้งเดิม (ไวต่อการหยุดชะงัก):

ป้อนคำอธิบายรูปภาพที่นี่

เห็นได้ชัดว่าเวอร์ชันใหม่ชนะ!

สำหรับการเปรียบเทียบเวอร์ชันกลางพร้อมกับ(XLOCK)อื่น ๆ จะสร้างแผนต่อไปนี้:

ป้อนคำอธิบายรูปภาพที่นี่

ฉันจะบอกว่าเป็นผู้ชนะ! ขอบคุณสำหรับความช่วยเหลือของทุกคน!


2
ควรใช้งานได้จริง แต่คุณกำลังใช้ SERIALIZABLE โดยที่ไม่สามารถใช้ได้ แถว Phantom ไม่มีอยู่ที่นี่ดังนั้นทำไมต้องใช้ระดับการแยกที่มีอยู่เพื่อป้องกันพวกเขา นอกจากนี้หากมีคนเรียกขั้นตอนของคุณจากที่อื่นหรือจากการเชื่อมต่อที่มีการเริ่มต้นธุรกรรมภายนอกการดำเนินการเพิ่มเติมใด ๆ ที่พวกเขาเริ่มต้นจะดำเนินการต่อที่ SERIALIZABLE ที่สามารถทำให้ยุ่ง
Mark Storey-Smith

2
SERIALIZABLEไม่มีเพื่อป้องกันภูตผีปีศาจ มันมีอยู่เพื่อให้ความหมายแยกอนุกรมต่อเนื่องคือผลกระทบต่อฐานข้อมูลเดียวกันราวกับว่าการทำธุรกรรมที่เกี่ยวข้องได้ดำเนินการตามลำดับในลำดับที่ไม่ระบุบางอย่าง
พอลไวท์พูดว่า GoFundMonica

6

ที่จะไม่ขโมยฟ้าร้องของ Mark Storey-Smith แต่เขากำลังเข้าสู่สิ่งที่โพสต์ของเขาด้านบน (ที่ได้รับ upvotes มากที่สุดโดยบังเอิญ) คำแนะนำที่ฉันให้ Max นั้นมีศูนย์กลางอยู่ที่โครงสร้าง "UPDATE set @variable = column = column + value" ซึ่งฉันคิดว่าเจ๋งมาก แต่ฉันคิดว่าอาจไม่มีเอกสาร (ต้องได้รับการสนับสนุนแม้ว่าจะมีเฉพาะสำหรับ TCP เท่านั้น มาตรฐาน)

นี่คือรูปแบบต่าง ๆ ของคำตอบของ Mark - เนื่องจากคุณคืนค่า ID ใหม่เป็นชุดระเบียนคุณสามารถทำได้ด้วยตัวแปรสเกลาร์ทั้งหมดไม่จำเป็นต้องทำธุรกรรมอย่างชัดเจนและฉันเห็นด้วยว่าการยุ่งกับระดับการแยกนั้นไม่จำเป็น เช่นกัน ผลที่ได้คือสะอาดและลื่นมาก ...

ALTER PROC [dbo].[GetNextID]
  @IDName nvarchar(255)
  AS
BEGIN
SET NOCOUNT ON;

DECLARE @Output TABLE ([NewID] INT);

UPDATE dbo.tblIDs SET LastID = LastID + 1
OUTPUT inserted.[LastId] INTO @Output
WHERE IDName = @IDName;

IF(@@ROWCOUNT = 1)
    SELECT [NewID] FROM @Output;
ELSE
    INSERT dbo.tblIDs (IDName, LastID)
    OUTPUT INSERTED.LastID AS [NewID]
    VALUES (@IDName,1);
END

3
ตกลงนี้ควรจะมีภูมิคุ้มกันต่อการหยุดชะงัก แต่ก็มีแนวโน้มที่จะมีสภาพการแข่งขันในการแทรกถ้าคุณไม่ทำธุรกรรม
Mark Storey-Smith

4

ฉันแก้ไขการหยุดชะงักที่คล้ายกันในระบบเมื่อปีที่แล้วโดยการเปลี่ยนสิ่งนี้:

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;

สำหรับสิ่งนี้:

UPDATE tblIDs SET LastID = @NewID WHERE IDName = @IDName;
IF @@ROWCOUNT = 0
BEGIN
  INSERT ...
END

โดยทั่วไปการเลือกCOUNTเพียงเพื่อพิจารณาว่ามีหรือไม่มีอยู่ค่อนข้างสิ้นเปลือง ในกรณีนี้เนื่องจากมันเป็น 0 หรือ 1 มันไม่เหมือนกับการทำงานมาก แต่ (ก) นิสัยที่สามารถทำให้เป็นเลือดออกในกรณีอื่น ๆ ซึ่งมันจะเสียค่าใช้จ่ายมาก (ในกรณีเหล่านั้นใช้IF NOT EXISTSแทนIF COUNT() = 0) และ (b) การสแกนเพิ่มเติมนั้นไม่จำเป็นอย่างสมบูรณ์ การUPDATEดำเนินการตรวจสอบเดียวกันเป็นหลัก

นอกจากนี้ดูเหมือนว่ากลิ่นรหัสร้ายแรงสำหรับฉัน:

SET @NewID = COALESCE((SELECT LastID FROM tblIDs WHERE IDName = @IDName),0)+1;

ประเด็นคืออะไรที่นี่? ทำไมไม่ใช้คอลัมน์ข้อมูลระบุตัวตนหรือรับลำดับนั้นโดยใช้ROW_NUMBER()ณ เวลาสืบค้น?


ตารางส่วนใหญ่ที่เราใช้IDENTITYอยู่ ตารางนี้รองรับรหัสดั้งเดิมที่เขียนใน MS Access ซึ่งจะเกี่ยวข้องกับการติดตั้งเพิ่มเติม SET @NewID=สายเพียงแค่เพิ่มค่าที่เก็บไว้ในตารางสำหรับ ID ที่กำหนด ( แต่คุณรู้อยู่แล้วว่า) คุณสามารถขยายวิธีการใช้งานได้ROW_NUMBER()ไหม?
Max Vernon

@ MaxVernon ไม่ทราบLastIDว่าแบบจำลองของคุณมีความหมายว่าอย่างไร วัตถุประสงค์ของมันคืออะไร? ชื่อนั้นไม่ได้อธิบายอย่างชัดเจน Access ใช้งานอย่างไร
แอรอนเบอร์ทรานด์ด์

ฟังก์ชันใน Access ต้องการเพิ่มแถวลงในตารางใดก็ตามที่ไม่มีรหัสประจำตัว First Access call GetNextID('WhatevertheIDFieldIsCalled')เพื่อรับ ID ถัดไปที่จะใช้จากนั้นแทรกมันลงในแถวใหม่พร้อมกับข้อมูลที่จำเป็น
Max Vernon

ฉันจะใช้การเปลี่ยนแปลงของคุณ กรณีบริสุทธิ์ของ "less is more"!
Max Vernon

1
การหยุดชะงักคงที่ของคุณอาจปรากฏขึ้นอีกครั้ง รูปแบบที่สองของคุณมีความเสี่ยงเช่นกัน: sqlblog.com/blogs/alexander_kuznetsov/archive/2010/01/12/… เพื่อกำจัดการหยุดชะงักฉันจะใช้ sp_getapplock อาจผสมระบบโหลดกับผู้ใช้หลายร้อยคนไม่มีการหยุดชะงัก
AK
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.