在SQL Server上插入更新存储的proc

时间:2020-03-05 18:40:08  来源:igfitidea点击:

我已经编写了一个存储的proc,如果存在记录,它将进行更新,否则它将进行插入。看起来像这样:

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

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

用这种方法的替代方法是先进行选择,然后根据返回的行数进行更新或者插入。我认为这效率低下,因为如果要进行更新,则会导致2个选择(第一个显式的select调用和第二个隐式的update调用)。如果proc要插入,那么效率不会有差异。

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

解决方案

回答

假设是正确的,这是执行此操作的最佳方法,这称为upsert / merge。

来自sqlservercentral.com的UPSERT的重要性:

For every update in the case mentioned above we are removing one
  additional read from the table if we
  use the UPSERT instead of EXISTS.
  Unfortunately for an Insert, both the
  UPSERT and IF EXISTS methods use the
  same number of reads on the table.
  Therefore the check for existence
  should only be done when there is a
  very valid reason to justify the
  additional I/O. The optimized way to
  do things is to make sure that you
  have little reads as possible on the
  DB.
  
  The best strategy is to attempt the
  update. If no rows are affected by the
  update then insert. In most
  circumstances, the row will already
  exist and only one I/O will be
  required.

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

回答

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

回答

如果要与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。

回答

UPSERT的忠实拥护者,确实减少了要管理的代码。这是我执行此操作的另一种方法:输入参数之一是ID,如果ID为NULL或者0,则说明它是INSERT,否则是更新。假设应用程序知道是否有一个ID,那么它将不会在所有情况下都能正常工作,但是如果有的话,它将把执行次数减少一半。

回答

逻辑听起来不错,但是如果我们传入了特定的主键,则可能需要考虑添加一些代码来防止插入。

否则,如果在更新不影响任何记录的情况下始终执行插入操作,那么当有人在我们运行" UPSERT"之前删除记录时会发生什么?现在,我们尝试更新的记录不存在,因此它将创建一条记录。那可能不是我们想要的行为。

回答

如果我们不在SQL 2008中进行合并,则必须将其更改为:

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

否则,如果更新由于某种原因而失败,则它将尝试并随后插入,因为失败的语句上的行数为0

回答

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

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检查和回滚可能是个好主意。

回答

请阅读我博客上的帖子,以获取可以使用的良好,安全的模式。有很多考虑因素,关于这个问题的公认答案远非安全。

为了快速回答,请尝试以下模式。它将在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