两个并发 API 调用在 SQL SERVER 中错误地更新数据

问题描述 投票:0回答:1

我有一个 API,可以添加新行或更新 SQL 表中的行。如果负载的

ID
大于
0
,那么它会根据给定的
ID
更新表,否则它会插入一个新行并返回新的
ID

这里是示例 SQL 片段:

BEGIN TRANSACTION; 

SET @FETCHEDID= (SELECT ID FROM dbo.SAMPLETABLE WHERE ID = @ID)
    
IF @FETCHEDID> 0

BEGIN
UPDATE dbo.SAMPLETABLE
SET [AMOUNT]=@AMOUNT,[DATECHANGED]=@DATEADDED,[LASTCHANGEDBYID]=@ADDEDBYID,
WHERE [ID]=@ID";
END

ELSE

BEGIN
INSERT INTO dbo.SAMPLETABLE([AMOUNT],[DATEADDED],[LASTCHANGEDBYID],[DATECHANGED])
VALUES (@AMOUNT,@DATEADDED,@ADDEDBYID,@DATEADDED);
END

COMMIT TRANSACTION;"

当使用

ID = 0
快速连续地从 UI 对该 API 进行两次连续的重复调用时,它最终会在数据库表中添加两行。 理想情况下,第一个 API 调用应该添加一个新行,第二个 API 调用应该用第一次调用返回的新
ID
更新该行。

调用是从 UI 进行的,负载中的

ID
只能在第一次成功调用时更新。这也可以通过避免重复调用在 UI 中处理。但我想确保后端也能够处理它。

第二个 API 调用是在更新之前从表中读取

ID
的陈旧值。

我们如何确保第二次 API 调用仅在第一次调用完成后才开始。

sql-server database concurrency race-condition consistency
1个回答
0
投票

这是使用

READ COMMITTED
隔离时的常见问题。

您执行此“Upsert”逻辑的方式是众所周知的反模式。锁定没有正确完成(它需要

UPDLOCK
),即使它仍然是低效的。

相反,只需

UPDATE
然后检查行数并有条件地
INSERT
.

SET XACT_ABORT ON;  -- needed to clean up transaction.

BEGIN TRANSACTION; 

UPDATE dbo.SAMPLETABLE WITH (HOLDLOCK, UPDLOCK)
SET AMOUNT = @AMOUNT,
    DATECHANGED = @DATEADDED,
    LASTCHANGEDBYID = @ADDEDBYID
WHERE ID = @ID;

IF @@ROWCOUNT = 0
    INSERT INTO dbo.SAMPLETABLE
      (AMOUNT, DATEADDED, LASTCHANGEDBYID, DATECHANGED)
    VALUES (@AMOUNT, @DATEADDED, @ADDEDBYID, @DATEADDED);

COMMIT;

如果插入比更新更有可能那么你可以翻转它。

SET XACT_ABORT ON;  -- needed to clean up transaction.

BEGIN TRANSACTION; 

INSERT INTO dbo.SAMPLETABLE
  (AMOUNT, DATEADDED, LASTCHANGEDBYID, DATECHANGED)
SELECT @AMOUNT, @DATEADDED, @ADDEDBYID, @DATEADDED)
WHERE NOT EXISTS (SELECT 1
    FROM dbo.SAMPLETABLE WITH (HOLDLOCK, UPDLOCK)
    WHERE ID = @ID);

IF @@ROWCOUNT = 0
    UPDATE dbo.SAMPLETABLE
    SET AMOUNT = @AMOUNT,
        DATECHANGED = @DATEADDED,
        LASTCHANGEDBYID = @ADDEDBYID
    WHERE ID = @ID;

COMMIT;
© www.soinside.com 2019 - 2024. All rights reserved.