当您需要根据某些条件执行INSERT,UPDATE或DELETE语句时,通常会出现这种情况。我的问题是,对命令性能的影响是否在命令之前添加了IF EXISTS。
实施例
IF EXISTS(SELECT 1 FROM Contacs WHERE [Type] = 1)
UPDATE Contacs SET [Deleted] = 1 WHERE [Type] = 1
INSERT或DELETE如何?
答案 0 :(得分:70)
我不完全确定,但我觉得这个问题实际上是关于upsert,这是以下原子操作:
UPDATE
目标; INSERT
该行进入目标; DELETE
来自目标的行。开发人员转向DBA经常天真地逐行编写,如下所示:
-- For each row in source
IF EXISTS(<target_expression>)
IF @delete_flag = 1
DELETE <target_expression>
ELSE
UPDATE target
SET <target_columns> = <source_values>
WHERE <target_expression>
ELSE
INSERT target (<target_columns>)
VALUES (<source_values>)
这是你能做的最糟糕的事情,原因如下:
它有竞争条件。该行可以在IF EXISTS
与后续DELETE
或UPDATE
之间消失。
这很浪费。对于每笔交易,您都要执行额外的操作;也许这是微不足道的,但这完全取决于你的索引程度。
最糟糕的是 - 它遵循一个迭代模型,在一行的层面上思考这些问题。这将对所有人的整体表现产生最大(最差)的影响。
一个非常小的(我强调次要的)优化就是尝试UPDATE
无论如何;如果该行不存在,@@ROWCOUNT
将为0,然后您可以“安全地”插入:
-- For each row in source
BEGIN TRAN
UPDATE target
SET <target_columns> = <source_values>
WHERE <target_expression>
IF (@@ROWCOUNT = 0)
INSERT target (<target_columns>)
VALUES (<source_values>)
COMMIT
最糟糕的是,这仍然会为每个事务执行两个操作,但至少只有一个机会只执行一个,它也消除了竞争条件(种类)。
但真正的问题是,这仍然是针对源中的每一行进行的。
在SQL Server 2008之前,你必须使用一个笨拙的3阶段模型来处理设置级别(仍然比逐行):
BEGIN TRAN
INSERT target (<target_columns>)
SELECT <source_columns> FROM source s
WHERE s.id NOT IN (SELECT id FROM target)
UPDATE t SET <target_columns> = <source_columns>
FROM target t
INNER JOIN source s ON t.d = s.id
DELETE t
FROM target t
WHERE t.id NOT IN (SELECT id FROM source)
COMMIT
正如我所说,在这方面表现相当糟糕,但仍然比一次一行的方法好很多。但是,SQL Server 2008最终引入了MERGE语法,所以现在你所要做的就是:
MERGE target
USING source ON target.id = source.id
WHEN MATCHED THEN UPDATE <target_columns> = <source_columns>
WHEN NOT MATCHED THEN INSERT (<target_columns>) VALUES (<source_columns>)
WHEN NOT MATCHED BY SOURCE THEN DELETE;
就是这样。一个声明。如果您正在使用SQL Server 2008并且需要执行INSERT
,UPDATE
和DELETE
的任何序列,具体取决于该行是否已存在 - 即使它只是一行行 - 没有借口不使用MERGE
。
如果您需要在事后找到所做的事情,您甚至可以OUTPUT
将受MERGE
影响的行{{1}}放入表变量中。简单,快速,无风险。做吧。
答案 1 :(得分:8)
仅对一次更新/删除/插入没有用 如果有几个操作员在条件之后,可能会增加性能 在最后一种情况下更好写
update a set .. where ..
if @@rowcount > 0
begin
..
end
答案 2 :(得分:4)
您不应该对UPDATE
和DELETE
执行此操作,就好像对性能有影响一样,不是肯定的。
对于INSERT
,可能会出现INSERT
会引发异常(UNIQUE CONSTRAINT
违规等)的情况,在这种情况下,您可能希望使用IF EXISTS
阻止它并且更优雅地处理它。
答案 3 :(得分:4)
无论
UPDATE … IF (@@ROWCOUNT = 0) INSERT
,也不
IF EXISTS(...) UPDATE ELSE INSERT
模式在高并发性下按预期工作。两者都可能失败。两者都可能经常失败。 MERGE是国王 - 它的表现要好得多。让我们做一些压力测试并亲自看看。
以下是我们将要使用的表格:
CREATE TABLE dbo.TwoINTs
(
ID INT NOT NULL PRIMARY KEY,
i1 INT NOT NULL ,
i2 INT NOT NULL ,
version ROWVERSION
) ;
GO
INSERT INTO dbo.TwoINTs
( ID, i1, i2 )
VALUES ( 1, 0, 0 ) ;
IF EXISTS(...)THEN模式经常在高并发性下失败。
让我们使用以下简单逻辑在循环中插入或更新行:如果存在具有给定ID的行,则更新它,否则插入新行。以下循环实现此逻辑。将其剪切并粘贴到两个选项卡中,在两个选项卡中切换到文本模式,然后同时运行它们。
-- hit Ctrl+T to execute in text mode
SET NOCOUNT ON ;
DECLARE @ID INT ;
SET @ID = 0 ;
WHILE @ID > -100000
BEGIN ;
SET @ID = ( SELECT MIN(ID)
FROM dbo.TwoINTs
) - 1 ;
BEGIN TRY ;
BEGIN TRANSACTION ;
IF EXISTS ( SELECT *
FROM dbo.TwoINTs
WHERE ID = @ID )
BEGIN ;
UPDATE dbo.TwoINTs
SET i1 = 1
WHERE ID = @ID ;
END ;
ELSE
BEGIN ;
INSERT INTO dbo.TwoINTs
( ID, i1, i2 )
VALUES ( @ID, 0, 0 ) ;
END ;
COMMIT ;
END TRY
BEGIN CATCH ;
ROLLBACK ;
SELECT error_message() ;
END CATCH ;
END ;
当我们在两个标签中同时运行此脚本时,我们将立即在两个标签中获得大量主键违规。这表明IF EXISTS模式在高并发性下执行时的不可靠性。
注意:此示例还演示如果我们在并发下使用SELECT MAX(ID)+1或SELECT MIN(ID)-1作为下一个可用的唯一值是不安全的。
答案 4 :(得分:3)
在大多数情况下,你不应该这样做。根据您的事务级别,您已经创建了一个竞争条件,现在在您的示例中,这并不重要,但数据可以从第一个选择更改为更新。而你所做的就是强制SQL做更多的工作
最明确的方法是测试这两个差异,看看哪一个给你适当的表现。
答案 5 :(得分:3)
IF EXISTS
基本上会做一个SELECT - 与UPDATE相同的那个。
因此,它会降低性能 - 如果没有更新,你做了相同的工作量(UPDATE会查询与你的选择相同的行缺乏)以及是否有更新的东西,你juet做了一个不需要的选择。
答案 6 :(得分:2)
IF EXISTS
声明的表现:
IF EXISTS(SELECT 1 FROM mytable WHERE someColumn = someValue)
取决于满足查询的索引。
答案 7 :(得分:2)
有一个轻微的影响,因为你进行了两次相同的检查,至少在你的例子中是这样的:
IF EXISTS(SELECT 1 FROM Contacs WHERE [Type] = 1)
必须查询,看看是否有,如果是,那么:
UPDATE Contacs SET [Deleted] = 1 WHERE [Type] = 1
必须查询,查看哪些...同样检查两次无缘无故。现在,如果您要查找的条件已编入索引,则它应该很快,但对于大型表格,您可能会因为运行选择而看到一些延迟。
答案 8 :(得分:2)
这在很大程度上重复了前面的(按时间)五个(不,六个)(没有,七个)答案,但是:
是的,您拥有的IF EXISTS结构将使数据库完成的工作翻倍。虽然IF EXISTS在找到第一个匹配的行时会“停止”(它不需要全部找到它们),但它仍然是额外的,最终毫无意义的努力 - 用于更新和删除。
无论哪种方式,你最终都会读完整个表或索引一次。但是,为什么要首先考虑IF EXISTS呢?
UPDATE Contacs SET [Deleted] = 1 WHERE [Type] = 1
无论是否有任何行要处理,或类似的DELETE都可以正常运行。没有行,表扫描,没有修改,你已经完成; 1行以上,扫描表格,应该修改的所有内容,再次完成。一次通过,没有大惊小怪,没有麻烦,不必担心“我的第一个查询和第二个查询之间的数据库被另一个用户更改了”。
INSERT是可能有用的情况 - 在添加行之前检查该行是否存在,以避免主键或唯一键违规。当然你必须担心并发 - 如果其他人试图在你同时添加这一行怎么办?将这一切包装成单个INSERT将在隐式事务中处理它(记住您的ACID属性!):
INSERT Contacs (col1, col2, etc) values (val1, val2, etc) where not exists (select 1 from Contacs where col1 = val1)
IF @@rowcount = 0 then <didn't insert, process accordingly>
答案 9 :(得分:1)
是的,这会影响表现(影响表现的程度会受到多种因素的影响)。实际上,您正在“两次”执行相同的查询(在您的示例中)。问问自己,在你的查询中是否需要保持这种防御性以及在什么情况下行不存在?此外,对于更新语句,受影响的行可能是确定是否已更新任何内容的更好方法。
答案 10 :(得分:0)
如果您使用的是MySQL,则可以使用insert ... on duplicate。
答案 11 :(得分:0)
IF EXISTS....UPDATE
不要这样做。它强制进行两次扫描/搜索,而不是一次。
如果更新在WHERE子句中找不到匹配项,则update语句的成本只是一个搜索/扫描。
如果确实找到了匹配项,并且如果你以IF EXISTS为前缀,它必须找到相同的匹配两次。在并发环境中,EXISTS的真实情况可能不再适用于UPDATE。
这正是UPDATE / DELETE / INSERT语句允许WHERE子句的原因。使用它!