SQL Server上INSERT或UPDATE的解决方案

Chris Cudmore 521 sql database sql-server insert upsert

假设一个表结构MyTable(KEY, datafield1, datafield2...).

通常我想要更新现有记录,或者如果新记录不存在则插入新记录.

实质上:

IF (key exists)
  run update command
ELSE
  run insert command

写这个的最佳表现方式是什么?

Keith.. 362

请参阅我之前非常相似的问题的详细答案

@Beau Crawford在SQL 2005及以下版本中是一个好方法,但是如果你授予代表它应该去找第一个人来做它.唯一的问题是,对于插入,它仍然是两个IO操作.

MS Sql2008 merge从SQL:2003标准中引入:

merge tablename with(HOLDLOCK) as target
using (values ('new value', 'different value'))
    as source (field1, field2)
    on target.idfield = 7
when matched then
    update
    set field1 = source.field1,
        field2 = source.field2,
        ...
when not matched then
    insert ( idfield, field1, field2, ... )
    values ( 7,  source.field1, source.field2, ... )

现在它真的只是一个IO操作,但可怕的代码:-(

  • 请参阅http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx,了解如何防止竞争条件导致即使使用时可能发生的错误`MERGE`语法. (25认同)
  • 这个答案真的需要更新,以解释Seph关于它没有HOLDLOCK时不是线程安全的评论.根据链接的帖子,MERGE隐式取出更新锁,但在插入行之前释放它,这可能导致争用条件和插入时的主键违规.通过使用HOLDLOCK,锁定将保持到插入发生之后. (11认同)
  • @Ian Boyd - 是的,这是SQL:2003标准的语法,而不是几乎所有其他数据库提供商决定支持的`upsert`.`upsert`语法是一种更好的方法,所以至少MS也应该支持它 - 它不是它是T-SQL中唯一的非标准关键字 (10认同)
  • @Seph这是一个真正的惊喜 - 在某种程度上是微软的失败:-SI猜测这意味着你需要一个`HOLDLOCK`用于高并发情况下的合并操作. (5认同)

aku.. 329

不要忘记交易.性能很好,但简单(IF EXISTS ..)方法非常危险.
当多个线程尝试执行插入或更新时,您可以轻松地获得主键冲突.

@Beau Crawford和@Esteban提供的解决方案显示了一般性的想法,但容易出错.

为了避免死锁和PK违规,您可以使用以下内容:

begin tran
if exists (select * from table with (updlock,serializable) where key = @key)
begin
   update table set ...
   where key = @key
end
else
begin
   insert into table (key, ...)
   values (@key, ...)
end
commit tran

要么

begin tran
   update table with (serializable) set ...
   where key = @key

   if @@rowcount = 0
   begin
      insert into table (key, ...) values (@key,..)
   end
commit tran

  • 这两种方法仍然可能失败.如果两个并发线程在同一行上执行相同操作,则第一个将成功,但第二个插入将因主键冲突而失败.即使由于记录存在而导致更新失败,事务也不保证插入成功.为了保证任何数量的并发事务都会成功,你必须使用一个锁. (26认同)
  • @aku你在BEGIN TRAN之前使用表提示("with(xxxx)")而不是"SET TRANSACTION ISOLATION LEVEL SERIALIZABLE"的任何理由? (6认同)
  • @CashCow,最后的胜利,这是INSERT或UPDATE应该做的:第一个插入,第二个更新记录.添加锁定允许在非常短的时间范围内发生,从而防止出错. (3认同)

用户甲.. 148

做一个UPSERT:

UPDATE MyTable SET FieldA=@FieldA WHERE Key=@Key

IF @@ROWCOUNT = 0
   INSERT INTO MyTable (FieldA) VALUES (@FieldA)

http://en.wikipedia.org/wiki/Upsert

  • @Triynko,我认为@Sam Saffron意味着如果两个+线程在正确的序列中交错,那么sql server将*throw*一个错误,表明主键违规*会发生*.将它包装在可序列化的事务中是防止上述语句集错误的正确方法. (16认同)
  • 如果应用了适当的唯一索引约束,则不应发生主键冲突.约束的重点是防止每次发生重复的行.尝试插入多少线程并不重要,数据库将根据需要进行序列化以强制执行约束......如果没有,则引擎无价值.当然,在序列化事务中包装它会使这更加正确,并且不易受死锁或插入失败的影响. (6认同)

Aaron Bertra.. 78

很多人会建议你使用MERGE,但我提醒你不要这样做.默认情况下,它不会保护您不受多个语句的并发和竞争条件的影响,但它确实会引入其他危险:

http://www.mssqltips.com/sqlservertip/3074/use-caution-with-sql-servers-merge-statement/

即使使用这种"更简单"的语法,我仍然更喜欢这种方法(为简洁省略了错误处理):

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
UPDATE dbo.table SET ... WHERE PK = @PK;
IF @@ROWCOUNT = 0
BEGIN
  INSERT dbo.table(PK, ...) SELECT @PK, ...;
END
COMMIT TRANSACTION;

很多人会这样建议:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
IF EXISTS (SELECT 1 FROM dbo.table WHERE PK = @PK)
BEGIN
  UPDATE ...
END
ELSE
  INSERT ...
END
COMMIT TRANSACTION;

但所有这一切都可以确保您可能需要两次读取表以找到要更新的行.在第一个示例中,您只需要找到一次行.(在这两种情况下,如果从初始读取中找不到行,则会发生插入.)

其他人会建议这样:

BEGIN TRY
  INSERT ...
END TRY
BEGIN CATCH
  IF ERROR_NUMBER() = 2627
    UPDATE ...
END CATCH

但是,如果除了在几乎每个插入失败的罕见情况下,除了让SQL Server捕获您可能首先阻止的异常之外没有其他原因,这是有问题的.我在这里证明了这一点:

  • @ user960567对不起,我并不总是实时收到评论通知. (9认同)
  • 很好的回答超过2年:) (3认同)
  • 如何插入/更新插入/更新许多记录的tem表? (2认同)

Esteban Aray.. 46

IF EXISTS (SELECT * FROM [Table] WHERE ID = rowID)
UPDATE [Table] SET propertyOne = propOne, property2 . . .
ELSE
INSERT INTO [Table] (propOne, propTwo . . .)

编辑:

唉,即使对我自己有害,我必须承认,没有选择的解决方案似乎更好,因为他们只需少一步即可完成任务.

  • 我还是比较喜欢这个.upsert看起来更像是通过副作用进行编程,而且我从来没有*看到那个初始选择的piddly小聚簇索引寻找导致真实数据库中的性能问题. (4认同)

Eric Weilnau.. 36

如果要一次UPSERT多个记录,可以使用ANSI SQL:2003 DML语句MERGE.

MERGE INTO table_name WITH (HOLDLOCK) USING table_name ON (condition)
WHEN MATCHED THEN UPDATE SET column1 = value1 [, column2 = value2 ...]
WHEN NOT MATCHED THEN INSERT (column1 [, column2 ...]) VALUES (value1 [, value2 ...])

在SQL Server 2005中查看模仿MERGE语句.

  • MERGE易受竞争条件的影响(请参阅http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx),除非您将其保留为certian锁.另外,看看MERGE在SQL Profiler中的性能......我发现它比其他解决方案更慢并且产生更多读取. (12认同)

用户甲.. 10

虽然对此发表评论已经很晚了,但我想用MERGE添加一个更完整的例子.

此类Insert + Update语句通常称为"Upsert"语句,可以使用SQL Server中的MERGE实现.

这里给出了一个很好的例子:http: //weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx

以上解释了锁定和并发方案.

我将引用相同的参考:

ALTER PROCEDURE dbo.Merge_Foo2
      @ID int
AS

SET NOCOUNT, XACT_ABORT ON;

MERGE dbo.Foo2 WITH (HOLDLOCK) AS f
USING (SELECT @ID AS ID) AS new_foo
      ON f.ID = new_foo.ID
WHEN MATCHED THEN
    UPDATE
            SET f.UpdateSpid = @@SPID,
            UpdateTime = SYSDATETIME()
WHEN NOT MATCHED THEN
    INSERT
      (
            ID,
            InsertSpid,
            InsertTime
      )
    VALUES
      (
            new_foo.ID,
            @@SPID,
            SYSDATETIME()
      );

RETURN @@ERROR;


用户甲.. 8

/*
CREATE TABLE ApplicationsDesSocietes (
   id                   INT IDENTITY(0,1)    NOT NULL,
   applicationId        INT                  NOT NULL,
   societeId            INT                  NOT NULL,
   suppression          BIT                  NULL,
   CONSTRAINT PK_APPLICATIONSDESSOCIETES PRIMARY KEY (id)
)
GO
--*/

DECLARE @applicationId INT = 81, @societeId INT = 43, @suppression BIT = 0

MERGE dbo.ApplicationsDesSocietes WITH (HOLDLOCK) AS target
--set the SOURCE table one row
USING (VALUES (@applicationId, @societeId, @suppression))
    AS source (applicationId, societeId, suppression)
    --here goes the ON join condition
    ON target.applicationId = source.applicationId and target.societeId = source.societeId
WHEN MATCHED THEN
    UPDATE
    --place your list of SET here
    SET target.suppression = source.suppression
WHEN NOT MATCHED THEN
    --insert a new line with the SOURCE table one row
    INSERT (applicationId, societeId, suppression)
    VALUES (source.applicationId, source.societeId, source.suppression);
GO

根据您的需要替换表和字段名称.注意使用ON条件.然后在DECLARE行上为变量设置适当的值(和类型).

干杯.


用户甲.. 5

您可以使用MERGEStatement,此语句用于插入数据(如果不存在)或更新(如果存在).

MERGE INTO Employee AS e
using EmployeeUpdate AS eu
ON e.EmployeeID = eu.EmployeeID`


提问时间:

查看次数:

542001 次

最近活跃:

8 月,1 周 前