解决方案对于插入或更新关SQL服务器
-
01-07-2019 - |
题
假设表结构 MyTable(KEY, datafield1, datafield2...)
.
我常常想要更新现有记录,或插入一个新的记录,如果它不存在。
基本上:
IF (key exists)
run update command
ELSE
run insert command
什么是最好的执行方式写这个?
解决方案
不要忘记交易。性能很好,但简单(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'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:
UPDATE MyTable SET FieldA=@FieldA WHERE Key=@Key IF @@ROWCOUNT = 0 INSERT INTO MyTable (FieldA) VALUES (@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;
但是,这一切的实现是确保你可能需要阅读表两次查找排(s)进行更新。在第一个样本,你只需要找到排(s)一次。(在这两种情况下,如果没有行被发现从最初读,插入的发生。)
其他建议这种方式:
BEGIN TRY
INSERT ...
END TRY
BEGIN CATCH
IF ERROR_NUMBER() = 2627
UPDATE ...
END CATCH
然而,这是有问题的,如果没有其他原因不是让SQL Server赶上的例外情况,你可以阻止在首位的是昂贵得多,除了在罕见的情形几乎每一插入失败。我证明多在这里:
IF EXISTS (SELECT * FROM [Table] WHERE ID = rowID)
UPDATE [Table] SET propertyOne = propOne, property2 . . .
ELSE
INSERT INTO [Table] (propOne, propTwo . . .)
修改:
唉,即使对我自己有害,我必须承认,没有选择的解决方案似乎更好,因为他们只需少一步即可完成任务。
如果您希望一次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 /宕/存档/ 2009/03/31 / UPSERT-RACE-条件与 - 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行上为变量设置适当的值(和类型)。
干杯。
您可以使用 MERGE
语句,此语句用于插入数据(如果不存在)或更新(如果存在)。
MERGE INTO Employee AS e
using EmployeeUpdate AS eu
ON e.EmployeeID = eu.EmployeeID`
如果更新UPDATE if-no-rows-then INSERT route,请考虑首先执行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。
使用MERGE可能更适合SQL2008以上版本。
在SQL Server 2008中,您可以使用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语句将非常受欢迎。
出于对这些贪婪的用户直接运行你的sprocs的恐惧而大家跳到HOLDLOCK-s之前:-)让我指出你必须保证新PK-s的独特性(身份)密钥,Oracle中的序列生成器,外部ID的唯一索引,索引覆盖的查询)。这是问题的阿尔法和欧米茄。如果你没有这个,那么Universe的HOLDLOCK-s不会保存你,如果你有,那么你在第一次选择时不需要UPDLOCK以外的任何东西(或者首先使用更新)。
Sprocs通常在非常受控的条件下运行,并假设有可信赖的呼叫者(中间层)。这意味着如果一个简单的upsert模式(更新+插入或合并)曾经看到重复PK,这意味着你的中间层或表设计中的错误,并且SQL会在这种情况下大吼大叫并拒绝记录。在这种情况下放置HOLDLOCK等于吃异常并接收可能有缺陷的数据,除了减少你的性能。
话虽如此,使用MERGE或UPDATE然后INSERT在您的服务器上更容易,并且更不容易出错,因为您不必记住添加(UPDLOCK)到第一次选择。此外,如果您以小批量进行插入/更新,则需要知道您的数据以确定交易是否合适。它只是一组不相关的记录,然后附加“包络”记录。交易将是有害的。
不会比赛的条件下真正的问题,如果你第一次尝试更新后插入?可以说,你有两个线程,需要设置一个值的关键 关键:
螺纹1:值=1
螺纹2:值=2
例的竞争条件的情况
- 关键 是不是定义
- 螺纹1失败有更新
- 螺纹2失败有更新
- 其中的一个线程1或螺纹2成功的插入。E.g。1线
另一个线程失败,并插入(有的错误重复的关键)-螺纹2。
- 结果是:"第一"的两个胎面插入,决定值。
- 想要的结果:最后在2线数据写入(更新或插入)应该决定值
但;在多线程的环境,操作系统的调度程序将决定在线程,以执行在上述情况,在那里我们有这个的竞争条件,它是操作系统,决定在序的执行。即:它是错误的说法,"线1"或"螺纹2"是"第一个"从全系统观点。
当时的执行是如此接近于螺纹1和线2,结果竞争条件并不重要。唯一的要求应该是一个线程应该定义得到的价值。
为实现:如更新后插入结果中的错误"重复的关键",这应该被视为成功。
此外,一个当然应该永远不会假定值数据库中相同的价值是你写的最后。
我曾尝试过以下解决方案,当插入语句的并发请求发生时,它对我有用。
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
您可以使用此查询。适用于所有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
注意:请解释答案否定
如果您使用ADO.NET,DataAdapter会处理此问题。
如果你想自己处理,这就是:
确保密钥列上存在主键约束。
然后你:
- 进行更新
- 如果更新失败,因为已存在包含密钥的记录,请执行插入操作。如果更新没有失败,则表示您已完成。 醇>
您也可以反过来执行此操作,即首先执行插入操作,如果插入失败则执行更新。通常第一种方式更好,因为更新比插入更频繁。
执行if exists ... else ...涉及最少两个请求(一个要检查,一个要采取行动)。以下方法只需要记录存在的一个,如果需要插入则需要两个:
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')
我通常会做其他几张海报所说的关于首先检查它然后做正确路径的事情。在执行此操作时您应该记住的一件事是,sql缓存的执行计划对于一个路径或另一个路径可能不是最佳的。我认为最好的方法是调用两个不同的存储过程。
FirstSP: If Exists Call SecondSP (UpdateProc) Else Call ThirdSP (InsertProc)
现在,我并不经常按照自己的建议行事,所以请耐心等待。
选择,如果得到结果,请更新它,如果没有,请创建它。