【问题标题】:Insert Update stored proc on SQL Server在 SQL Server 上插入更新存储过程
【发布时间】:2010-09-06 00:37:42
【问题描述】:

我编写了一个存储过程,如果记录存在,它将执行更新,否则它将执行插入。它看起来像这样:

update myTable set Col1=@col1, Col2=@col2 where ID=@ID
if @@rowcount = 0
insert into myTable (Col1, Col2) values (@col1, @col2)

我以这种方式编写它的逻辑是,更新将使用 where 子句执行隐式选择,如果返回 0,则插入将发生。

这样做的替代方法是进行选择,然后根据返回的行数进行更新或插入。我认为这是低效的,因为如果您要进行更新,它将导致 2 次选择(第一次显式选择调用和第二次隐式在更新的位置)。如果proc要进行插入,那么效率就没有差异。

我的逻辑在这里合理吗? 这是您将插入和更新组合到存储过程中的方式吗?

【问题讨论】:

    标签: sql sql-server stored-procedures upsert


    【解决方案1】:

    您的假设是正确的,这是实现它的最佳方式,它被称为upsert/merge

    Importance of UPSERT - from sqlservercentral.com

    对于上述案例中的每个更新,我们都会删除一个 如果我们从表中额外读取 使用 UPSERT 而不是 EXISTS。 不幸的是,对于插入,两者 UPSERT 和 IF EXISTS 方法使用 表上相同数量的读取。 因此检查是否存在 应该只在有 非常正当的理由来证明 额外的 I/O。优化的方法 做事是为了确保你 尽可能少读 数据库。

    最好的策略是尝试 更新。如果没有行受 更新然后插入。多数情况 情况下,该行将已经 存在并且只有一个 I/O 必填。

    编辑: 请查看this answer 和链接的博客文章,了解此模式存在的问题以及如何使其安全运行。

    【讨论】:

    • 嗯,我认为它至少回答了一个问题。而且我没有添加代码,因为问题中的代码似乎已经适合我了。虽然我会把它放在一个事务中,但我没有考虑更新的隔离级别。感谢您在回答中指出这一点!
    【解决方案2】:

    顺便说一下,MERGE 是 SQL Server 2008 中的新功能之一。

    【讨论】:

    【解决方案3】:

    如果要与 SQL Server 2000/2005 一起使用,则需要将原始代码包含在事务中,以确保数据在并发场景中保持一致。

    BEGIN TRANSACTION Upsert
    update myTable set Col1=@col1, Col2=@col2 where ID=@ID
    if @@rowcount = 0
    insert into myTable (Col1, Col2) values (@col1, @col2)
    COMMIT TRANSACTION Upsert
    

    这会产生额外的性能成本,但会确保数据完整性。

    添加,如前所述,应在可用的情况下使用 MERGE。

    【讨论】:

      【解决方案4】:

      UPSERT 的忠实拥护者,确实减少了要管理的代码。这是我这样做的另一种方法:其中一个输入参数是 ID,如果 ID 为 NULL 或 0,则您知道它是一个 INSERT,否则它是一个更新。假设应用程序知道是否有 ID,因此不会在所有情况下都有效,但如果您知道,则会将执行次数减半。

      【讨论】:

        【解决方案5】:

        您的逻辑似乎是合理的,但如果您传入了特定的主键,您可能需要考虑添加一些代码来防止插入。

        否则,如果您总是在更新不影响任何记录的情况下进行插入,那么当有人在您“UPSERT”运行之前删除记录时会发生什么?现在您尝试更新的记录不存在,因此它将创建一条记录。这可能不是您要寻找的行为。

        【讨论】:

          【解决方案6】:

          如果您没有在 SQL 2008 中进行合并,则必须将其更改为:

          如果@@rowcount = 0 且@@error=0

          否则,如果由于某种原因更新失败,那么它会在之后尝试插入,因为失败语句的行数为 0

          【讨论】:

            【解决方案7】:

            你不仅需要在事务中运行它,它还需要高隔离级别。我实际上默认隔离级别是已提交读,并且此代码需要可序列化。

            SET transaction isolation level SERIALIZABLE
            BEGIN TRANSACTION Upsert
            UPDATE myTable set Col1=@col1, Col2=@col2 where ID=@ID
            if @@rowcount = 0
              begin
                INSERT into myTable (ID, Col1, Col2) values (@ID @col1, @col2)
              end
            COMMIT TRANSACTION Upsert
            

            也许还添加@@error 检查和回滚可能是个好主意。

            【讨论】:

            • @Munish Goyal 因为在数据库中,多个命令和程序并行运行。然后其他线程可以在更新运行之后和插入运行之前插入一行。
            【解决方案8】:

            请阅读post on my blog 以了解您可以使用的良好、安全的模式。有很多考虑因素,这个问题的公认答案远非安全。

            如需快速回答,请尝试以下模式。它可以在 SQL 2000 及更高版本上正常工作。 SQL 2005 为您提供了打开其他选项的错误处理,SQL 2008 为您提供了一个 MERGE 命令。

            begin tran
               update t with (serializable)
               set hitCount = hitCount + 1
               where pk = @id
               if @@rowcount = 0
               begin
                  insert t (pk, hitCount)
                  values (@id,1)
               end
            commit tran
            

            【讨论】:

            • 在您的博客文章中,您以在存在性检查中使用 WITH(updlock, serializable) 提示结束。然而,阅读 MSDN 的状态是:“UPDLOCK - 指定更新锁将被获取并保持,直到事务完成。”这是否意味着可序列化提示是多余的,因为无论如何都会在事务的其余部分持有更新锁,还是我误解了什么?
            【解决方案9】:

            修改后的 Dima Malenko 帖子:

            SET TRANSACTION ISOLATION LEVEL SERIALIZABLE 
            
            BEGIN TRANSACTION UPSERT 
            
            UPDATE MYTABLE 
            SET    COL1 = @col1, 
                   COL2 = @col2 
            WHERE  ID = @ID 
            
            IF @@rowcount = 0 
              BEGIN 
                  INSERT INTO MYTABLE 
                              (ID, 
                               COL1, 
                               COL2) 
                  VALUES      (@ID, 
                               @col1, 
                               @col2) 
              END 
            
            IF @@Error > 0 
              BEGIN 
                  INSERT INTO MYERRORTABLE 
                              (ID, 
                               COL1, 
                               COL2) 
                  VALUES      (@ID, 
                               @col1, 
                               @col2) 
              END 
            
            COMMIT TRANSACTION UPSERT 
            

            您可以捕获错误并将记录发送到失败的插入表。
            我需要这样做,因为我们正在获取通过 WSDL 发送的任何数据,并在可能的情况下在内部对其进行修复。

            【讨论】:

              猜你喜欢
              • 2012-10-13
              • 1970-01-01
              • 1970-01-01
              • 2017-12-02
              • 2015-03-02
              • 1970-01-01
              • 2023-04-07
              • 1970-01-01
              相关资源
              最近更新 更多