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)
许多人会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或线程2中的一个成功插入。 例如线程1
-
另一个线程失败,插入(与错误重复键) – 线程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将处理此操作。
如果你想自己处理,就是这样的:
确保您的密钥列上有一个主键约束。
然后你:
- 做更新
- 如果更新失败,因为具有密钥的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它。