SQL Server上的INSERT OR UPDATE解决scheme

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

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

主要有:

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

什么是最好的performance方式来写这个?

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

@Beau Crawford&@Esteban提供的解决scheme显示了一般想法,但容易出错。

为了避免死锁和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 

看到我的一个非常类似的问题的详细答案

@Beau Crawford's在SQL 2005及以下版本中是一个很好的方法,但是如果你授予代表权,那么它应该是第一个接受它的人 。 唯一的问题是插入它仍然是两个IO操作。

MS Sql2008引入了SQL:2003标准的merge

 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操作,但是可怕的代码:-(

做一个UPSERT:

更新MyTable SET FieldA = @ FieldA WHERE Key = @ Key

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

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

许多人会build议你使用MERGE ,但我警告你不MERGE 。 默认情况下,它不会保护您不受并发和竞争条件的影响,而是会引入其他危险:

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

即使使用这个“更简单”的语法,我仍然更喜欢这种方法(为简洁起见省略了error handling):

 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; 

很多人会这样build议:

 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; 

但是,所有这一切都是为了确保您可能需要读取表格两次以find要更新的行。 在第一个示例中,您只需要定位行(s)一次。 (在这两种情况下,如果从初始读取中没有find行,则会发生插入。)

其他人会这样build议:

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

但是,如果没有其他原因,让SQL Server捕获exception,那么首先要防止的exception要昂贵得多,除非几乎每个插入失败的罕见情况中,否则这是有问题的。 我在这里certificate:

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

编辑:

唉,即使对我自己也不利,我必须承认,做这个没有select的解决scheme似乎更好,因为他们只需less一步就可以完成任务。

如果您想一次写入多条logging,则可以使用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语句 。

尽pipe对此的评论很晚,但我想用MERGE添加更完整的示例。

这种Insert + Update语句通常被称为“Upsert”语句,可以使用SQL Server中的MERGE来实现。

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

上面解释了locking和并发情况。

我将引用相同的参考文献:

 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; 
 /* 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 

根据需要replace表和字段名称。 注意使用ON条件。 然后在DECLARE行上为variables设置适当的值(和types)。

干杯。

您可以使用MERGE语句,如果不存在,则使用此语句插入数据,如果存在则更新。

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

在SQL Server 2008中,您可以使用MERGE语句

如果要更新if-no-rows-update然后INSERT路由,考虑先执行INSERT以防止竞争条件(假设没有插入的DELETE)

 INSERT INTO MyTable (Key, FieldA) SELECT @Key, @FieldA WHERE NOT EXISTS ( SELECT * FROM MyTable WHERE Key = @Key ) IF @@ROWCOUNT = 0 BEGIN UPDATE MyTable SET FieldA=@FieldA WHERE Key=@Key IF @@ROWCOUNT = 0 ... record was deleted, consider looping to re-run the INSERT, or RAISERROR ... END 

除了避免竞争条件,如果在大多数情况下logging已经存在,那么这将导致INSERT失败,浪费CPU。

使用MERGE可能更适用于SQL2008以上。

MS SQL Server 2008引入了MERGE语句,我相信它是SQL:2003标准的一部分。 正如许多人已经表明,处理一个行的情况并不是什么大事,但是当处理大的数据集时,需要一个游标,带来所有的性能问题。 在处理大型数据集时,MERGE语句将受到欢迎。

在所有人都跳槽到HOLDLOCK-s之前,这些用户直接运行你的sprocs :-)让我指出, 你必须保证新的PK-s的独特性(devise中使用身份键,序列生成器,外部ID-s,索引覆盖的查询)。 这是问题的阿尔法和欧米茄。 如果你没有,那么宇宙的HOLDLOCK-s就不会拯救你,如果你有这个,那么在第一次select(或者首先使用更新)时,你不需要任何超越UPDLOCK的东西。

Sprocs通常在非常受控制的条件下运行,并假设可信来电者(中间层)。 这意味着如果一个简单的upsert模式(update + insert或merge)看到重复的PK,这意味着你的中间层或表devise中的一个错误,那么SQL会在这种情况下大喊一个错误并拒绝logging。 在这种情况下放置一个HOLDLOCK等于除了减less你的表演以外,还可以吃掉例外,并收集潜在的错误数据。

话虽如此,使用MERGE或UPDATE,那么INSERT在您的服务器上更容易,而且不会出现错误,因为您不必记住添加(UPDLOCK)来首选。 另外,如果你正在做小批量的插入/更新,你需要知道你的数据,以决定一个事务是否合适。 它只是一个无关logging的集合,那么额外的“包络”交易将是有害的。

比赛条件真的很重要,如果你第一次尝试更新,然后插入? 比方说,你有两个线程想要为键值设置一个值:

线程1:值= 1
线程2:值= 2

示例竞赛条件情况

  1. 没有定义
  2. 线程1失败,更新
  3. 线程2更新失败
  4. 线程1或线程2中的一个成功插入。 例如线程1
  5. 另一个线程失败,插入(与错误重复键) – 线程2。

    • 结果:插入的两个踏板的“第一个”决定价值。
    • 想要的结果:写入数据(更新或插入)的最后2个线程应该决定值

但; 在multithreading环境下,OS调度程序决定线程执行的顺序 – 在上面的场景中,我们有这种竞争条件,就是决定执行顺序的OS。 即:从系统的观点来看,“线程1”或“线程2”是“第一”是错误的。

当线程1和线程2的执行时间如此接近时,竞态条件的结果并不重要。 唯一的要求应该是其中一个线程应该定义结果值。

对于实现:如果更新,然后插入结果错误“重复密钥”,这应该被视为成功。

另外,当然也不应该假定数据库中的值与上次写入的值相同。

这取决于使用模式。 一个人不得不看细节中的用法大图。 例如,如果创buildlogging后使用模式是99%更新,那么“UPSERT”是最好的解决scheme。

在第一次插入(命中)之后,它将是所有单个语句更新,没有ifs或buts。 插入的'where'条件是必要的,否则它将插入重复项,而你不想处理locking。

 UPDATE <tableName> SET <field>=@field WHERE key=@key; IF @@ROWCOUNT = 0 BEGIN INSERT INTO <tableName> (field) SELECT @field WHERE NOT EXISTS (select * from tableName where key = @key); END 

我已经尝试了下面的解决scheme,并为我工作,当并发请求插入语句发生。

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

如果您使用ADO.NET,则DataAdapter将处理此操作。

如果你想自己处理,就是这样的:

确保您的密钥列上有一个主键约束。

然后你:

  1. 做更新
  2. 如果更新失败,因为具有密钥的logging已经存在,请执行插入操作。 如果更新没有失败,则完成。

你也可以这样做,即先插入,如果插入失败,则进行更新。 通常情况下,第一种方式更好,因为更新比插入更频繁。

你可以使用这个查询。 在所有SQL Server版本中工作。 这很简单,清楚。 但是你需要使用2个查询。 如果你不能使用MERGE,你可以使用

  BEGIN TRAN UPDATE table SET Id = @ID, Description = @Description WHERE Id = @Id INSERT INTO table(Id, Description) SELECT @Id, @Description WHERE NOT EXISTS (SELECT NULL FROM table WHERE Id = @Id) COMMIT TRAN 

注意:请解释回答否定

做一个如果存在…其他…涉及做两个请求最低(一个检查,一个采取行动)。 以下方法只需要一个存在logging的地方,如果需要插入则需要两个:

 DECLARE @RowExists bit SET @RowExists = 0 UPDATE MyTable SET DataField1 = 'xxx', @RowExists = 1 WHERE Key = 123 IF @RowExists = 0 INSERT INTO MyTable (Key, DataField1) VALUES (123, 'xxx') 

我通常会做其他几个海报,就先检查一下,然后做正确的道路。 有一件事你应该记住,当这样做是由SQLcaching的执行计划可能是一个path或其他的最佳。 我相信最好的办法是调用两个不同的存储过程。

 FirstSP:
如果存在
   调用SecondSP(UpdateProc)
其他
   调用ThirdSP(InsertProc)

现在,我不经常听从自己的build议,所以拿一粒盐来。

做一个select,如果你得到一个结果,更新它,如果没有,创build它。