我正在尝试实现您的基本UPSERT功能,但要有所不同:有时我不想实际更新现有行。

本质上,我正在尝试在不同存储库之间同步某些数据,而Upsert函数似乎是解决之道。因此,主要基于Sam Saffron's answer to this question以及其他一些研究和阅读,我想到了这个存储过程:

(注意:我使用的是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

对于基本的Upsert,我对此很满意,但我希望实际更新取决于另一列的值。可以将其视为“锁定”一行,这样Upsert过程就不会进行进一步的更新。我可以这样更改UPDATE语句:
UPDATE dbo.Item WITH (SERIALIZABLE)
SET Title = @pTitle,
    Teaser = @pTeaser
WHERE ContentID = @pContentID
AND RowLocked = false

但是,随后的Insert尝试插入已存在但由于“锁定”而未更新的行时,将因唯一约束冲突(针对ContentID字段)而失败。

那么这是否意味着我不再拥有经典的Upsert,即我每次都必须选择该行以确定是否可以更新或插入该行?我敢肯定是这样,所以我想我真正要问的是帮助正确设置事务隔离级别,以便过程可以安全地执行。

最佳答案

我拍了下面的脚本来证明我几年来使用的这个技巧。如果使用它,则需要对其进行修改以适合您的目的。评论如下:

/*
CREATE TABLE Item
 (
   Title      varchar(255)  not null
  ,Teaser     varchar(255)  not null
  ,ContentId  varchar(30)  not null
  ,RowLocked  bit  not null
)


UPDATE item
 set RowLocked = 1
 where ContentId = 'Test01'

*/


DECLARE
  @Check varchar(30)
 ,@pContentID varchar(30)
 ,@pTitle varchar(255)
 ,@pTeaser varchar(255)

set @pContentID = 'Test01'
set @pTitle     = 'TestingTitle'
set @pTeaser    = 'TestingTeasier'

set @check = null

UPDATE dbo.Item
 set
   @Check = ContentId
  ,Title  = @pTitle
  ,Teaser = @pTeaser
 where ContentID = @pContentID
  and RowLocked = 0

print isnull(@check, '<check is null>')

IF @Check is null
    INSERT dbo.Item (ContentID, Title, Teaser, RowLocked)
     values (@pContentID, @pTitle, @pTeaser, 0)

select * from Item

这里的技巧是您可以在Update语句中的局部变量中设置值。上面,仅当更新有效(即满足更新条件)时,才设置“标志”值;否则,它不会被更改(此处保留为null),您可以进行检查并进行相应处理。

至于事务并使其可序列化,在建议如何进行操作之前,我想了解有关事务中必须封装的内容的更多信息。

-附录,以下第二条评论的后续行动-----------

Saffron先生的想法是实现此例程的一种彻底而可靠的方法,因为您的主键是在外部定义的并传递到数据库中(即您不使用标识列,对我来说很好,它们经常被过度使用)。

我进行了更多测试(在ContentId列上添加了主键约束,在事务中包装了UPDATE和INSERT,在更新中添加了可序列化的提示),是的,这应该可以完成您想要的一切。失败的更新在索引的该部分上施加了范围锁定,这将阻止任何同时尝试在该列中插入该新值的尝试。当然,如果同时提交了N个请求,则“第一个”将创建该行,第二个,第三个等将立即更新该行-除非您沿该行的某个位置设置了“锁定”。好招!

(请注意,在键列上没有索引的情况下,您将锁定整个表。此外,范围锁可能会将行锁定在新值的“任一侧”上,或者可能不会,可以测试一个。这没关系,因为操作的持续时间应为[?]单位毫秒。)

关于sql - 如何实现条件Upsert存储过程?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/1106717/

10-13 00:51