如何实现条件Upsert存储过程?

Mat*_*att 6 sql sql-server stored-procedures sql-server-2005 upsert

我正在尝试实现您的基本UPSERT功能,但有一点扭曲:有时我不想实际更新现有行.

本质上我正在尝试在不同的存储库之间同步一些数据,并且Upsert函数似乎是要走的路.所以主要基于Sam Saffron对这个问题的回答,以及其他一些研究和阅读,我提出了这个存储过程:

(注意:我正在使用MS SQL Server 2005,因此MERGE语句不是一个选项)

CREATE PROCEDURE [dbo].[usp_UpsertItem] 
    -- Add the parameters for the stored procedure here
    @pContentID varchar(30) = null, 
    @pTitle varchar(255) = null,
    @pTeaser varchar(255) = null 
AS
BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

    BEGIN TRANSACTION

        UPDATE dbo.Item WITH (SERIALIZABLE)
        SET Title = @pTitle,
            Teaser = @pTeaser
        WHERE ContentID = @pContentID

        IF @@rowcount = 0
            INSERT INTO dbo.Item (ContentID, Title, Teaser)
            VALUES (@pContentID, @pTitle, @pTeaser)

    COMMIT TRANSACTION
END
Run Code Online (Sandbox Code Playgroud)

对于基本的Upsert我很满意,但是我想让实际的更新以另一列的值为条件.可以将其视为"锁定"一行,以便Upsert过程不会进行进一步的更新.我可以像这样更改UPDATE语句:

UPDATE dbo.Item WITH (SERIALIZABLE)
SET Title = @pTitle,
    Teaser = @pTeaser
WHERE ContentID = @pContentID
AND RowLocked = false
Run Code Online (Sandbox Code Playgroud)

但是当后续插入将尝试插入已存在但由于"已锁定"而未更新的行时,后续插入将因唯一约束违规(针对ContentID字段)而失败.

那么这是否意味着我不再拥有经典的Upsert,即我每次都必须选择行来确定它是否可以更新或插入?我认为是这种情况,所以我想我真正要求的是帮助确保事务隔离级别正确,以便程序安全执行.

A-K*_*A-K 9

一个非常常见的问题.有些方法在高并发性下并不成功.这里描述和压力测试:

压力测试UPSERT

防御性数据库编程:消除IF语句.

在这种情况下,仅仅编写一些代码是不够的,您需要将其暴露给高并发性.例如,我不确定我是否理解CptSkippy推荐的内容,但以下内容演示了如何进行压力测试.设置表和程序:

CREATE TABLE [dbo].[TwoINTs](
      [ID] [int] NOT NULL,
      [i1] [int] NOT NULL,
      [i2] [int] NOT NULL,
      [i3] [int] NOT NULL
);
CREATE PROCEDURE dbo.SaveTwoINTs(@ID INT, @i1 INT, @i2 INT)
AS
BEGIN
      SET NOCOUNT ON;
      SET XACT_ABORT OFF;
      SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
      DECLARE @ret INT;
      SET @ret=0;
      BEGIN TRAN; 
IF EXISTS(SELECT 1 FROM dbo.TwoINTs WHERE ID=@ID) BEGIN
      UPDATE dbo.TwoINTs WITH (SERIALIZABLE)
         SET i1=i1+@i1, i2=i2+@i2 WHERE ID=@ID;
      SET @ret=@@ERROR;
END ELSE BEGIN
     INSERT INTO dbo.TwoINTs(ID, i1, i2, i3)VALUES(@ID, @i1, @i2, @i1);
      SET @ret=@@ERROR;
END;
COMMIT;
RETURN @ret;
END
GO
Run Code Online (Sandbox Code Playgroud)

设置两个执行该过程的循环:

CREATE PROCEDURE Testers.UpsertLoop1
AS
BEGIN
DECLARE @ID INT, @i1 INT, @i2 INT, @count INT, @ret INT;
SET @count = 0;
WHILE @count<50000 BEGIN
      SELECT @ID = COALESCE(MAX(ID),0) + 1 FROM dbo.TwoInts;
    EXEC @ret=dbo.SaveTwoINTs @ID, 1, 0;
      SET @count = @count + 1;
END;
END;
GO
CREATE PROCEDURE Testers.UpsertLoop2
AS
BEGIN
DECLARE @ID INT, @i1 INT, @i2 INT, @count INT, @ret INT;
SET @count = 0;
WHILE @count<50000 BEGIN
      SELECT @ID = COALESCE(MAX(ID),0) + 1 FROM dbo.TwoInts;
    EXEC @ret=dbo.SaveTwoINTs @ID, 0, 1;
      SET @count = @count + 1;
END;
END;
Run Code Online (Sandbox Code Playgroud)

在两个选项卡中执行这些过程,并亲眼看看您遇到了很多错误:

Testers.UpsertLoop1 --run in one tab
Testers.UpsertLoop1 --run in one tab

Msg 2601, Level 14, State 1, Procedure SaveTwoINTs, Line 15
Cannot insert duplicate key row in object 'dbo.TwoINTs' with unique index 'UNQ_TwoInts_ID'.
The statement has been terminated.
Run Code Online (Sandbox Code Playgroud)

按照我提供的链接查看在并发下实际工作的方法.


Ste*_*eet -1

您可以切换更新/插入的顺序。因此,您可以在 try/catch 中进行插入,如果违反约束,则进行更新。不过感觉有点脏。