在 SQL Server 上插入更新存储过程
Posted
技术标签:
【中文标题】在 SQL Server 上插入更新存储过程【英文标题】:Insert Update stored proc on 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要进行插入,那么效率就没有差异。
我的逻辑在这里合理吗? 这是您将插入和更新组合到存储过程中的方式吗?
【问题讨论】:
【参考方案1】:您的假设是正确的,这是实现它的最佳方式,它被称为upsert/merge。
Importance of UPSERT - from sqlservercentral.com:
对于上述案例中的每个更新,我们都会删除一个 如果我们从表中额外读取 使用 UPSERT 而不是 EXISTS。 不幸的是,对于插入,两者 UPSERT 和 IF EXISTS 方法使用 表上相同数量的读取。 因此检查是否存在 应该只在有 非常正当的理由来证明 额外的 I/O。优化的方法 做事是为了确保你 尽可能少读 数据库。
最好的策略是尝试 更新。如果没有行受 更新然后插入。多数情况 情况下,该行将已经 存在并且只有一个 I/O 必填。
编辑: 请查看this answer 和链接的博客文章,了解此模式存在的问题以及如何使其安全运行。
【讨论】:
嗯,我认为它至少回答了一个问题。而且我没有添加代码,因为问题中的代码似乎已经适合我了。虽然我会把它放在一个事务中,但我没有考虑更新的隔离级别。感谢您在回答中指出这一点!【参考方案2】:请阅读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 - 指定更新锁将被获取并保持,直到事务完成。”这是否意味着可序列化提示是多余的,因为无论如何都会在事务的其余部分持有更新锁,还是我误解了什么?【参考方案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】:顺便说一下,MERGE 是 SQL Server 2008 中的新功能之一。
【讨论】:
你绝对应该使用它而不是这种难以阅读的自制废话。很好的例子在这里 - mssqltips.com/sqlservertip/1704/… 您应该小心使用 MERGE,因为在锁定方面存在一些复杂的“魔法”。 the official docs 中描述的很少【参考方案5】:你不仅需要在事务中运行它,它还需要高隔离级别。我实际上默认隔离级别是已提交读,并且此代码需要可序列化。
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 因为在数据库中,多个命令和程序并行运行。然后其他线程可以在更新运行之后和插入运行之前插入一行。【参考方案6】:如果您没有在 SQL 2008 中进行合并,则必须将其更改为:
如果@@rowcount = 0 且@@error=0
否则,如果由于某种原因更新失败,那么它会在之后尝试插入,因为失败语句的行数为 0
【讨论】:
【参考方案7】:UPSERT 的忠实拥护者,真正减少了要管理的代码。这是我这样做的另一种方法:其中一个输入参数是 ID,如果 ID 为 NULL 或 0,则您知道它是一个 INSERT,否则它是一个更新。假设应用程序知道是否有 ID,因此不会在所有情况下都有效,但如果您知道,则会将执行次数减半。
【讨论】:
【参考方案8】:修改后的 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 发送的任何数据,并在可能的情况下在内部对其进行修复。
【讨论】:
【参考方案9】:您的逻辑似乎是合理的,但如果您传入了特定的主键,您可能需要考虑添加一些代码来防止插入。
否则,如果您总是在更新不影响任何记录的情况下进行插入,那么当有人在您“UPSERT”运行之前删除记录时会发生什么?现在您尝试更新的记录不存在,因此它将创建一条记录。这可能不是您要寻找的行为。
【讨论】:
以上是关于在 SQL Server 上插入更新存储过程的主要内容,如果未能解决你的问题,请参考以下文章
sql server中批量插入与更新两种解决方案分享(存储过程)
在 SQL Server 2008 R2 的 MERGE 语句中更新插入的记录