Xử lý truy cập đồng thời vào một bảng chính mà không bị bế tắc trong SQL Server


32

Tôi có một bảng được sử dụng bởi một ứng dụng cũ để thay thế cho IDENTITYcác trường trong các bảng khác nhau.

Mỗi hàng trong bảng lưu trữ ID được sử dụng cuối cùng LastIDcho trường có tên IDName.

Đôi khi, Proc được lưu trữ bị bế tắc - Tôi tin rằng tôi đã xây dựng một trình xử lý lỗi thích hợp; tuy nhiên tôi quan tâm xem liệu phương pháp này có hiệu quả như tôi nghĩ hay không, nếu tôi sủa sai cây ở đây.

Tôi khá chắc chắn nên có một cách để truy cập vào bảng này mà không có bất kỳ sự bế tắc nào cả.

Cơ sở dữ liệu được cấu hình với READ_COMMITTED_SNAPSHOT = 1 .

Đầu tiên, đây là bảng:

CREATE TABLE [dbo].[tblIDs](
    [IDListID] [int] NOT NULL 
        CONSTRAINT PK_tblIDs 
        PRIMARY KEY CLUSTERED 
        IDENTITY(1,1) ,
    [IDName] [nvarchar](255) NULL,
    [LastID] [int] NULL,
);

Và chỉ mục không bao gồm trên IDNametrường:

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

Một số dữ liệu mẫu:

INSERT INTO tblIDs (IDName, LastID) 
    VALUES ('SomeTestID', 1);
INSERT INTO tblIDs (IDName, LastID) 
    VALUES ('SomeOtherTestID', 1);
GO

Quy trình được lưu trữ được sử dụng để cập nhật các giá trị được lưu trữ trong bảng và trả về ID tiếp theo:

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

Mẫu thực thi của Proc được lưu trữ:

EXEC GetNextID 'SomeTestID';

NewID
2

EXEC GetNextID 'SomeTestID';

NewID
3

EXEC GetNextID 'SomeOtherTestID';

NewID
2

CHỈNH SỬA:

Tôi đã thêm một chỉ mục mới, vì chỉ mục hiện tại IX_tblIDs_Name không được SP sử dụng; Tôi giả sử bộ xử lý truy vấn đang sử dụng chỉ mục được nhóm vì nó cần giá trị được lưu trữ trong LastID. Dù sao, chỉ số này IS được sử dụng bởi kế hoạch thực hiện thực tế:

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:

Tôi đã thực hiện lời khuyên mà @AaronBertrand đã đưa ra và sửa đổi nó một chút. Ý tưởng chung ở đây là tinh chỉnh tuyên bố để loại bỏ việc khóa không cần thiết, và tổng thể để làm cho SP hiệu quả hơn.

Đoạn code dưới đây thay thế mã trên từ 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;

Vì mã của chúng tôi không bao giờ thêm bản ghi vào bảng này với 0 trong nên LastIDchúng tôi có thể đưa ra giả định rằng nếu @NewID là 1 thì ý định sẽ thêm ID mới vào danh sách, ngoài ra chúng tôi đang cập nhật một hàng hiện có trong danh sách.


Cách bạn định cấu hình cơ sở dữ liệu để hỗ trợ RCSI là không liên quan. Bạn đang cố tình leo thang đến SERIALIZABLEđây.
Aaron Bertrand

vâng, tôi chỉ muốn thêm tất cả thông tin liên quan. Tôi rất vui vì bạn đang xác nhận rằng nó không liên quan!
Max Vernon

Rất dễ để sp_getapplock trở thành nạn nhân bế tắc, nhưng không phải nếu bạn bắt đầu giao dịch, hãy gọi sp_getapplock một lần để có được khóa độc quyền và tiếp tục sửa đổi.
AK

1
IDName có phải là duy nhất không? Sau đó giới thiệu lại "tạo chỉ mục không độc nhất ". Tuy nhiên, nếu bạn cần giá trị null thì chỉ mục cũng cần được lọc .
crokusek

Câu trả lời:


15

Đầu tiên, tôi sẽ tránh thực hiện một chuyến đi khứ hồi đến cơ sở dữ liệu cho mọi giá trị. Ví dụ: nếu ứng dụng của bạn biết nó cần 20 ID mới, đừng thực hiện 20 chuyến khứ hồi. Chỉ thực hiện một cuộc gọi thủ tục được lưu trữ và tăng bộ đếm lên 20. Ngoài ra, có thể tốt hơn nếu chia bảng của bạn thành nhiều bảng.

Có thể tránh bế tắc hoàn toàn. Tôi không có bế tắc nào trong hệ thống của mình. Có một số cách để thực hiện điều đó. Tôi sẽ chỉ cho tôi cách sử dụng sp_getapplock để loại bỏ bế tắc. Tôi không biết điều này có hiệu quả với bạn không, vì SQL Server là nguồn đóng, vì vậy tôi không thể xem mã nguồn và vì vậy tôi không biết liệu tôi đã kiểm tra tất cả các trường hợp có thể chưa.

Dưới đây mô tả những gì làm việc cho tôi. YMMV.

Đầu tiên, chúng ta hãy bắt đầu với một kịch bản mà chúng ta luôn nhận được một số lượng bế tắc đáng kể. Thứ hai, chúng ta sẽ sử dụng sp_getapplock loại bỏ chúng. Điểm quan trọng nhất ở đây là để kiểm tra căng thẳng giải pháp của bạn. Giải pháp của bạn có thể khác, nhưng bạn cần đưa ra giải pháp đồng thời cao, vì tôi sẽ trình bày sau.

Điều kiện tiên quyết

Hãy để chúng tôi thiết lập một bảng với một số dữ liệu thử nghiệm:

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

Hai thủ tục sau đây hoàn toàn có khả năng xảy ra trong bế tắc:

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

Sinh sản bế tắc

Các vòng lặp sau sẽ tái tạo hơn 20 khóa chết mỗi khi bạn chạy chúng. Nếu bạn nhận được ít hơn 20, tăng số lần lặp.

Trong một tab, chạy này;

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 ;

Trong một tab khác, chạy tập lệnh này.

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 ;

Hãy chắc chắn rằng bạn bắt đầu cả hai trong vài giây.

Sử dụng sp_getapplock để loại bỏ bế tắc

Thay đổi cả hai thủ tục, chạy lại vòng lặp và thấy rằng bạn không còn bế tắc:

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

Sử dụng bảng có một hàng để loại bỏ bế tắc

Thay vì gọi sp_getapplock, chúng ta có thể sửa đổi bảng sau:

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);

Khi chúng ta đã tạo và điền vào bảng này, chúng ta có thể thay thế dòng sau

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

với cái này, trong cả hai thủ tục:

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

Bạn có thể chạy lại bài kiểm tra căng thẳng và tự mình thấy rằng chúng tôi không có bế tắc.

Phần kết luận

Như chúng ta đã thấy, sp_getapplock có thể được sử dụng để tuần tự hóa quyền truy cập vào các tài nguyên khác. Vì vậy, nó có thể được sử dụng để loại bỏ bế tắc.

Tất nhiên, điều này có thể làm chậm đáng kể sửa đổi. Để giải quyết điều đó, chúng ta cần chọn độ chi tiết phù hợp cho khóa độc quyền và bất cứ khi nào có thể, hãy làm việc với các bộ thay vì các hàng riêng lẻ.

Trước khi sử dụng phương pháp này, bạn cần phải tự kiểm tra nó. Đầu tiên, bạn cần chắc chắn rằng bạn nhận được ít nhất vài chục bế tắc với cách tiếp cận ban đầu của bạn. Thứ hai, bạn sẽ không gặp bế tắc khi chạy lại tập lệnh repro tương tự bằng cách sử dụng quy trình lưu trữ đã sửa đổi.

Nói chung, tôi không nghĩ có một cách hay để xác định liệu T-SQL của bạn có an toàn trước những bế tắc chỉ bằng cách nhìn vào nó hay nhìn vào kế hoạch thực hiện. IMO cách duy nhất để xác định xem mã của bạn có dễ bị bế tắc hay không là đưa nó ra mức độ đồng thời cao.

Chúc may mắn với việc loại bỏ bế tắc! Chúng tôi hoàn toàn không có bất kỳ sự bế tắc nào trong hệ thống của mình, điều này rất tốt cho sự cân bằng giữa công việc và cuộc sống của chúng tôi.


2
+1 là sp_getapplock là một công cụ hữu ích không được biết đến nhiều. Đưa ra một 'mớ hỗn độn khủng khiếp có thể mất thời gian để tháo gỡ, đó là một mẹo hữu ích để nối tiếp một quá trình bế tắc. Nhưng, liệu nó có phải là lựa chọn đầu tiên cho một trường hợp như thế này dễ hiểu và có thể (có lẽ) nên được xử lý bằng các cơ chế khóa tiêu chuẩn?
Mark Storey-Smith

2
@ MarkStorey-Smith Đây là lựa chọn đầu tiên của tôi vì tôi đã nghiên cứu và căng thẳng chỉ thử nghiệm nó một lần và tôi có thể sử dụng lại nó trong mọi tình huống - việc xê-ri hóa đã xảy ra, vì vậy mọi thứ xảy ra sau khi sp_getapplock không ảnh hưởng đến kết quả. Với các cơ chế khóa tiêu chuẩn, tôi không bao giờ có thể chắc chắn như vậy - việc thêm một chỉ mục hoặc chỉ nhận một kế hoạch thực hiện khác có thể gây ra bế tắc mà trước đó không có. Hỏi tôi làm sao tôi biết.
AK

Tôi đoán tôi đang thiếu một cái gì đó rõ ràng, nhưng làm thế nào để sử dụng UPDATE dbo.DeadlockTestMutex SET Toggle = 1 - Toggle WHERE ID = 1;ngăn chặn bế tắc?
Dale K

9

Sử dụng XLOCKgợi ý về SELECTcách tiếp cận của bạn hoặc sau đây UPDATEsẽ miễn dịch với loại bế tắc này:

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;

Sẽ trở lại với một vài biến thể khác (nếu không bị đánh bại!).


Mặc dù XLOCKsẽ ngăn không cho bộ đếm hiện tại được cập nhật từ nhiều kết nối, nhưng bạn có cần TABLOCKXngăn không cho nhiều kết nối thêm cùng một bộ đếm mới không?
Dale K

1
@DaleBurrell Không, bạn có PK hoặc ràng buộc duy nhất trên IDName.
Mark Storey-Smith

7

Mike Defehr chỉ cho tôi một cách thanh lịch để thực hiện điều này một cách rất nhẹ nhàng:

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

(Để đầy đủ, đây là bảng được liên kết với Proc được lưu trữ)

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

Đây là kế hoạch thực hiện cho phiên bản mới nhất:

nhập mô tả hình ảnh ở đây

Và đây là kế hoạch thực hiện cho phiên bản gốc (dễ bị bế tắc):

nhập mô tả hình ảnh ở đây

Rõ ràng, phiên bản mới chiến thắng!

Để so sánh, phiên bản trung gian với (XLOCK)vv, tạo ra kế hoạch sau:

nhập mô tả hình ảnh ở đây

Tôi muốn nói rằng đó là một chiến thắng! Cảm ơn sự giúp đỡ của mọi người!


2
Thực sự nên hoạt động nhưng bạn đang sử dụng SERIALIZABLE khi không áp dụng được. Hàng ma không thể tồn tại ở đây, vậy tại sao lại sử dụng mức cô lập tồn tại để ngăn chặn chúng? Ngoài ra, nếu ai đó gọi thủ tục của bạn từ người khác hoặc từ một kết nối nơi bắt đầu giao dịch bên ngoài, mọi hành động tiếp theo mà họ thực hiện sẽ được tiến hành tại SERIALIZABLE. Điều đó có thể nhận được lộn xộn.
Mark Storey-Smith

2
SERIALIZABLEkhông tồn tại để ngăn chặn phantoms. Nó tồn tại để cung cấp ngữ nghĩa cách ly tuần tự hóa , nghĩa là cùng một hiệu ứng liên tục trên cơ sở dữ liệu như thể các giao dịch liên quan đã thực hiện theo kiểu thanh toán theo một thứ tự không xác định.
Paul White nói GoFundMonica

6

Không phải để đánh cắp sấm sét của Mark Storey-Smith, nhưng anh ta đã đăng lên một cái gì đó với bài đăng của mình ở trên (điều đó đã tình cờ nhận được nhiều sự ủng hộ nhất). Lời khuyên mà tôi đã đưa ra cho Max tập trung vào cấu trúc "CẬP NHẬT @variable = cột = cột + giá trị" mà tôi thấy thực sự tuyệt vời, nhưng tôi nghĩ có thể không có giấy tờ (mặc dù nó phải được hỗ trợ bởi TCP điểm chuẩn).

Đây là một biến thể của câu trả lời của Mark - bởi vì bạn đang trả về giá trị ID mới dưới dạng tập bản ghi, bạn hoàn toàn có thể loại bỏ biến vô hướng, không cần giao dịch rõ ràng và tôi đồng ý rằng việc lộn xộn với các mức cô lập là không cần thiết cũng. Kết quả là rất sạch sẽ và đẹp ...

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
Đồng ý điều này sẽ miễn nhiễm với bế tắc nhưng nó dễ xảy ra tình trạng chạy đua khi chèn, nếu bạn bỏ qua giao dịch.
Mark Storey-Smith

4

Tôi đã sửa một bế tắc tương tự trong một hệ thống năm ngoái bằng cách thay đổi điều này:

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;

Về điều này:

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

Nói chung, việc chọn COUNTchỉ để xác định sự hiện diện hay vắng mặt là khá lãng phí. Trong trường hợp này vì nó là 0 hoặc 1, nó không giống như nhiều công việc, nhưng (a) thói quen đó có thể chảy vào các trường hợp khác mà nó sẽ tốn kém hơn nhiều (trong những trường hợp đó, sử dụng IF NOT EXISTSthay vì IF COUNT() = 0) và (b) việc quét bổ sung là hoàn toàn không cần thiết. CácUPDATE thực hiện về cơ bản cùng kiểm tra.

Ngoài ra, nó trông giống như một mùi mã nghiêm trọng đối với tôi:

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

Điểm ở đây là gì? Tại sao không sử dụng một cột danh tính hoặc lấy ra chuỗi đó bằng cách sử dụng ROW_NUMBER()tại thời điểm truy vấn?


Hầu hết các bảng chúng tôi đang sử dụng một IDENTITY. Bảng này hỗ trợ một số mã kế thừa được viết bằng MS Access có liên quan khá nhiều đến việc trang bị thêm. Các SET @NewID=dòng chỉ đơn giản là tăng giá trị được lưu trữ trong bảng cho ID nhất định (nhưng bạn đã biết rằng). Bạn có thể mở rộng về cách tôi có thể sử dụng ROW_NUMBER()?
Max Vernon

@MaxVernon không phải không biết ý LastIDnghĩa thực sự của mô hình của bạn. Mục đích của nó là gì? Tên không chính xác tự giải thích. Access sử dụng nó như thế nào?
Aaron Bertrand

Một hàm trong Access muốn thêm một hàng vào bất kỳ bảng đã cho nào không có IDENTITY. Truy cập lần đầu tiên GetNextID('WhatevertheIDFieldIsCalled')để có được ID tiếp theo sử dụng, sau đó chèn nó vào hàng mới cùng với bất kỳ dữ liệu nào là cần thiết.
Max Vernon

Tôi sẽ thực hiện thay đổi của bạn. Một trường hợp thuần túy "ít hơn là nhiều"!
Max Vernon

1
Bế tắc cố định của bạn có thể xuất hiện trở lại. Mẫu thứ hai của bạn cũng dễ bị tổn thương: sqlblog.com/bloss/alexander_kuznetsov/archive/2010/01/12/. Để loại bỏ bế tắc tôi sẽ sử dụng sp_getapplock. Có thể hệ thống tải hỗn hợp với hàng trăm người dùng không có bế tắc.
AK
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.