如果在INSERT,UPDATE,DELETE之前存在优化

当需要根据某些条件执行INSERT,UPDATE或DELETE语句时,经常会出现这种情况。 而我的问题是,对查询性能的影响是否在命令之前添加IF EXISTS。

IF EXISTS(SELECT 1 FROM Contacs WHERE [Type] = 1) UPDATE Contacs SET [Deleted] = 1 WHERE [Type] = 1 

什么INSERT或删除?

我不完全确定,但我得到的印象是这个问题真的是upsert,这是以下primefaces操作:

  • 如果该行存在于源和目标中,则UPDATE目标;
  • 如果行只存在于源中,则将行插入到目标中;
  • (可选)如果行存在于目标中,但存在,则从目标中DELETE行。

开发人员变身的数据库pipe理员经常天真地将它逐行写入,如下所示:

 -- 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和随后的DELETEUPDATE之间消失。

  • 这是浪费。 对于每一笔交易,您都有额外的操作。 也许这是微不足道的,但这完全取决于你的索引。

  • 最糟糕的是,它是遵循一个迭代模型,在单行的层面上考虑这些问题。 这将对整体performance产生最大(最差)的影响。

一个非常小的(我强调次要的)优化是只是试图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 

最坏的情况是,每次交易都会执行两次操作,但至less有一次只能执行一次,也会消除竞争条件(种类)。

但真正的问题在于,对于源代码中的每一行,这个问题仍然存在。

在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 td = s.id DELETE t FROM target t WHERE t.id NOT IN (SELECT id FROM source) COMMIT 

正如我所说的,在这方面的performance相当糟糕,但仍然比一行一行的方法好很多。 但是,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,并且需要根据行是否已经存在来执行任何序列的INSERTUPDATEDELETE即使它只是一行 ,也没有理由不使用MERGE

你甚至可以把受MERGE影响的行OUTPUT到一个表variables中,如果你以后需要找出所做的事情的话。 简单,快速,无风险 这样做。

这只是一个更新/删除/插入没有用。
如果有几个操作员,如果条件满足,可能会增加性能。
在最后的情况下写得更好

 update a set .. where .. if @@rowcount > 0 begin .. end 

你不应该这样做的UPDATEDELETE ,就好像对性能有影响 ,这不是一个积极的

对于INSERT ,可能会出现INSERT会引发exception( UNIQUE CONSTRAINT违规等)的情况,在这种情况下,您可能希望使用IF EXISTS来阻止它,并更优雅地处理它。

也不

 UPDATE … IF (@@ROWCOUNT = 0) INSERT 

也不

 IF EXISTS(...) UPDATE ELSE INSERT 

模式在高并发的情况下按预期工作。 两者都可能失败。 两者都可能经常失败。 MERGE是国王 – 拥有更好的performance。让我们做一些压力testing,看看我们自己。

这里是我们将要使用的表格:

 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作为下一个可用的唯一值是不安全的。

大多数情况下你不应该这样做。 根据您的交易级别,您创build了竞争条件,现在在您的示例中,这并不重要,但数据可以从第一个select更改为更新。 而你所做的只是强迫SQL去做更多的工作

要知道的最好的方法是testing两个差异,看看哪一个给你适当的performance。

IF EXISTS基本上做一个SELECT – 与UPDATE相同。

因此,它会降低性能 – 如果没有什么可更新的话,你做了相同的工作量(UPDATE将查询与你的select相同的行数),如果有更新的东西,juet做了一个不需要的select。

IF EXISTS语句的性能:

 IF EXISTS(SELECT 1 FROM mytable WHERE someColumn = someValue) 

取决于目前满足查询的索引。

有一个小的影响,因为你做了两次相同的检查,至less在你的例子中:

 IF EXISTS(SELECT 1 FROM Contacs WHERE [Type] = 1) 

有查询,看看有没有,如果是真的那么:

 UPDATE Contacs SET [Deleted] = 1 WHERE [Type] = 1 

有查询,看看哪些…没有理由相同的检查两次。 现在,如果你正在寻找的条件被索引,它应该是快速的,但是对于大型的表,你可能会看到一些延迟,因为你正在运行select。

这在很大程度上重复了前面的(时间)五(不,六)(不,七)答案,但是:

是的,你所拥有的IF EXISTS结构将会使数据库所做的工作翻倍。 当IF EXISTS在find第一个匹配行(它不需要全部find它们)时会“停止”,但是对于更新和删除来说,这仍然是额外的和最终没有意义的努力。

  • 如果不存在这样的行,IF EXISTS将进行完整的扫描(表或索引)来确定这个。
  • 如果存在一个或多个这样的行,那么IF EXISTS将读取足够的表/索引以查找第一个,然后UPDATE或DELETE将重新读取该表以再次查找并处理它 – 并且将读取“桌子的其余部分”,看看是否还有更多需要处理的东西。 (如果索引正确,但仍然足够快。)

所以不pipe怎样,你最终都会读完整个表格或索引至less一次。 但是,为什么一开始就为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> 

是的,这会影响性能(性能受到多大影响的程度会受到影响)。 实际上,你正在做同样的查询“两次”(在你的例子中)。 问问你自己,你是否需要在你的查询中保持这种防御,以及在哪些情况下行不在那里? 另外,对于更新语句,受影响的行可能是更好的方式来确定是否有更新。

 IF EXISTS....UPDATE 

不要这样做。 它迫使两个扫描/寻求而不是一个。

如果更新没有在WHERE子句中find匹配项,则更新语句的成本只是一个查找/扫描。

如果它find了一个匹配项,并且如果你的前缀是IF IF EXTSTS,那么它必须find两次相同的匹配项。 而在并发环境中,EXISTS的真实情况可能不再适用于UPDATE。

这正是为什么UPDATE / DELETE / INSERT语句允许WHERE子句的原因。 用它!

如果你正在使用MySQL,那么你可以使用插入…重复 。