假设表结构为MyTable(KEY, datafield1, datafield2...)
。
我通常想更新现有记录,或者如果不存在则插入新记录。
实质上:
IF (key exists)
run update command
ELSE
run insert command
编写此代码的最佳方式是什么?
假设表结构为MyTable(KEY, datafield1, datafield2...)
。
我通常想更新现有记录,或者如果不存在则插入新记录。
实质上:
IF (key exists)
run update command
ELSE
run insert command
编写此代码的最佳方式是什么?
Answers:
不要忘记交易。性能不错,但是简单的(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
@Beau Crawford的方法在SQL 2005及更低版本的SQL中是一种好方法,尽管如果您授予rep,则应该由第一个人使用。唯一的问题是,对于插入来说,它仍然是两个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操作,但是代码很糟糕:-(
upsert
所有其他数据库提供者都决定支持的语法。该upsert
语法是要做到这一点远更好的方式,所以至少是MS应该有太多支持-它不象它在T-SQL中唯一的非标准关键字
MERGE
。
HOLDLOCK
在高并发情况下进行合并操作。
进行UPSERT:
更新MyTable SET FieldA = @ FieldA WHERE Key = @ Key 如果@@ ROWCOUNT = 0 插入MyTable(FieldA)值(@FieldA)
很多人会建议您使用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捕获本来可以避免的异常以外的其他任何原因而导致的代价昂贵得多的话,这是有问题的,除非在极少数情况下几乎每个插入都会失败。我在这里证明了很多:
UPDATE target SET col = tmp.col FROM target INNER JOIN #tmp ON <key clause>; INSERT target(...) SELECT ... FROM #tmp AS t WHERE NOT EXISTS (SELECT 1 FROM target WHERE key = t.key);
IF EXISTS (SELECT * FROM [Table] WHERE ID = rowID)
UPDATE [Table] SET propertyOne = propOne, property2 . . .
ELSE
INSERT INTO [Table] (propOne, propTwo . . .)
编辑:
las,即使对我自己不利,我也必须承认这样做的解决方案似乎没有更好的选择,因为它们只需一步就可以完成任务。
如果要一次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 ...])
尽管对此评论还为时已晚,但我想使用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;
/*
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行上为变量设置适当的值(和类型)。
干杯。
如果要先执行UPDATE if-no-rows-updated然后执行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
除了避免出现竞争状况外,如果在大多数情况下该记录将已经存在,则这将导致INSERT失败,从而浪费CPU。
从SQL2008起,最好使用MERGE。
这取决于使用模式。必须从整体上了解使用情况,而又不会迷失在细节上。例如,如果创建记录后使用模式更新为99%,则“ UPSERT”是最佳解决方案。
在第一次插入(命中)之后,将所有单条语句更新,而不是ifs或buts。插入中的“ where”条件是必需的,否则它将插入重复项,并且您不想处理锁定。
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
MS SQL Server 2008引入了MERGE语句,我认为它是SQL:2003标准的一部分。正如许多人所表明的,处理一行情况不是什么大问题,但是在处理大型数据集时,需要一个游标,随之而来的是所有性能问题。在处理大型数据集时,将非常欢迎MERGE语句。
在每个人都出于直接运行这些存储过程的这些臭名昭著的用户的担心而跳入HOLDLOCK-s之前:-)让我指出,您必须通过设计保证新PK-s的唯一性(身份密钥,Oracle中的序列生成器,唯一索引)外部ID,索引覆盖的查询)。这就是问题的阿尔法和欧米茄。如果您没有该功能,那么宇宙中的任何HOLDLOCK-都不会拯救您,如果您有,那么您不需要在UPDLOCK之外的任何内容(或先使用更新)。
Sproc通常在受严格控制的条件下运行,并且假定调用方是受信任的(中间层)。这意味着,如果一个简单的upsert模式(update + insert或merge)曾经看到过重复的PK,这意味着您的中间层或表设计中存在错误,那么在这种情况下SQL会大吼大叫并拒绝记录是件好事。在这种情况下,放置一个HOLDLOCK等于吃异常,并吸收潜在的错误数据,除了降低性能。
话虽这么说,使用MERGE或UPDATE然后在服务器上使用INSERT更加容易,并且更容易出错,因为您不必记住要先选择添加(UPDLOCK)。另外,如果您要进行小批量的插入/更新,则需要了解您的数据,以便确定交易是否合适。它只是不相关记录的集合,因此其他“包络”交易将是有害的。
如果您先尝试进行更新,然后执行插入操作,那么比赛条件真的重要吗?假设您有两个线程想要为key key设置值:
线程1:值= 1
线程2:值= 2
竞赛条件场景示例
另一个线程因插入(带有错误重复键)而失败-线程2。
但; 在多线程环境中,OS调度程序决定线程执行的顺序-在上述场景中,在我们具有这种竞争条件的情况下,正是OS决定了执行顺序。即:从系统的角度说“线程1”或“线程2”是“第一”是错误的。
当线程1和线程2的执行时间如此接近时,竞争条件的结果无关紧要。唯一的要求应该是其中一个线程应定义结果值。
对于实现:如果更新后执行插入导致错误“重复键”,则应将其视为成功。
另外,当然不应假定数据库中的值与您最后写入的值相同。
在SQL Server 2008中,您可以使用MERGE语句
您可以使用此查询。在所有SQL Server版本中均可使用。简单明了。但是您需要使用2个查询。如果不能使用,可以使用
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
注意:请解释否定答案
如果使用ADO.NET,则DataAdapter会处理此问题。
如果您想自己处理,可以这样:
确保您的键列上存在主键约束。
然后你:
您也可以采用另一种方法,即先插入,如果插入失败,则进行更新。通常,第一种方法更好,因为更新比插入要频繁。
我通常会做其他一些发帖人所说的,首先检查它是否存在,然后再执行正确的路径。执行此操作时应记住的一件事是,对于一个路径或另一路径,sql缓存的执行计划可能不是最佳的。我相信最好的方法是调用两个不同的存储过程。
FirstSP: 如果存在 致电SecondSP(UpdateProc) 其他 致电ThirdSP(InsertProc)
现在,我不再经常听取自己的建议,所以要加些盐。