2010-02-16 24 views
33

當您需要根據某些條件執行INSERT,UPDATE或DELETE語句時,經常會出現這種情況。我的問題是,對查詢性能的影響是否在命令之前添加IF EXISTS。如果在INSERT,UPDATE或DELETE之前存在優化

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

什麼插入或刪除?

+0

爲什麼不看執行計劃? – RichardOD

回答

65

我不能完全肯定,但我得到的印象是,這個問題實際上是關於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和隨後的DELETEUPDATE之間消失。

  • 這是浪費。對於每一筆交易,您都會執行額外的操作;也許這是微不足道的,但這完全取決於你編制索引的程度。

  • 最糟糕的是 - 它遵循一個迭代模型,在單行的層面上考慮這些問題。這將對整體表現產生最大(最差)的影響。

一個很小的(我強調次要的)優化是隻是試圖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之前,您必須使用一個尷尬的三階段模型,在設定的水平(仍高於行由行更好)來處理這個:

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和需要根據該行是否已經存在執行的INSERTUPDATEDELETE任何序列 - 即使它只是一個行 - 有沒有藉口不使用MERGE

您甚至可以將OUTPUT受到MERGE影響的行轉換爲表變量,如果您需要事後瞭解所做的事情。簡單,快速,無風險。做到這一點。

+2

感謝您向我介紹MERGE。這真的很酷。 – jwarzech

+7

MERGE非常酷 - 想指出,因爲我剛剛發現了這一點:MERGE不會避免競爭條件,除非添加適當的提示('WITH(HOLDLOCK)')。參見[Dan Guzman關於用MERGE指出UPSERT競爭狀態的工作](http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx) –

+1

+ 1代表「開發人員變身的DBA經常天真地將它逐行寫入」! –

2

IF EXISTS語句的性能:

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

取決於當前滿足查詢索引。

3

你不應該在大多數情況下這樣做。根據您的交易級別,您創建了競爭條件,現在在您的示例中,這並不重要,但數據可以從第一次選擇更改爲更新。而你所做的一切都是強迫SQL去做更多的工作

要知道肯定的最好方法是測試兩個差異,看看哪一個能給你適當的性能。

2

有輕微的影響,因爲你在做相同的檢查兩次,至少在你的榜樣:

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

具有查詢,看看是否有任何,如果爲true:

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

必須查詢,查看哪些...沒有理由的相同檢查兩次。現在,如果你正在尋找的條件被索引,它應該是快速的,但對於大型表格,你可能會看到一些延遲,因爲你正在運行select。

1

是的,這會影響性能(影響性能的程度會受到很多因素的影響)。實際上你正在做兩次相同的查詢(在你的例子中)。問問你自己,你是否需要在你的查詢中保持這種防禦性,以及在哪些情況下行不在那裏?另外,對於更新語句,受影響的行可能是確定是否更新任何內容的更好方法。

3

IF EXISTS基本上會做一個SELECT - 與UPDATE相同。

因此,它會降低性能 - 如果沒有什麼更新,你沒工作的相同數量(UPDATE會質疑同樣缺乏行作爲你的選擇),如果有什麼更新,你juet做一個不需要的選擇。

+0

另一種方法是使用burnall的答案:只要先進行更新,然後檢查@@ rowcount。如果它是零,那麼沒有任何更新,你打電話通過插入。 –

+0

'IF EXISTS'是**不是**插入或更新操作的好設計。它有一個競爭條件,即使在'BEGIN TRAN' /'COMMIT TRAN'塊內。 – Aaronaught

+0

@Aarounaught - 好點。刪除了那一塊。 – DVK

8

這對一次更新/刪除/插入沒有用。
如果有條件後有多個運算符,可能會增加性能。
在最後一種情況下寫的更好

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

+1。我必須承認,在一讀這個問題時,至少對我而言這並不明顯,這是OP所指的。必須遲到... –

+0

正是。做什麼*通常*是需要的行動,檢查結果,然後在必要時做備用案例。 –

+0

+1我還要走這條路,但他的問題並不清楚......好的回答 – JoshBerke

4

UPDATEDELETE你不應該這樣做,因爲如果對性能的影響,它是不是一個積極之一。

對於INSERT可能有情況下您INSERT將引發一個異常(UNIQUE CONSTRAINT違規等),在這種情況下,你可能想阻止它與IF EXISTS和更加妥善地處理它。

2

這在很大程度上重複前面的(按時間)五(無,六)(不,七)的答案,但是:

是,如果存在則是你必須和大量將增加一倍,所做的工作結構數據庫。雖然IF EXISTS在找到第一個匹配行(它不需要全部找到它們)時會「停止」,但對於更新和刪除而言,它仍然是額外的且毫無意義的工作。

  • 如果不存在這樣的行,那麼IF EXISTS將進行完整掃描(表或索引)以確定此行。
  • 如果存在一個或多個此類行,IF EXISTS將讀取足夠的表/索引以查找第一個,然後UPDATE或DELETE將重新讀取該表以再次找到並處理它 - 它會讀取表格的「剩餘部分」以查看是否還有其他要處理的內容。 (如果索引正確,但仍然足夠快)

因此無論哪種方式,您最終都會讀取整個表格或索引至少一次。但是,爲什麼首先要打擾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> 
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(...),那麼模式下高併發經常失敗。

讓我們使用以下簡單邏輯插入或更新循環中的行:如果存在給定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作爲下一個可用唯一值,則不安全。

0
IF EXISTS....UPDATE 

不要這樣做。它強制兩次掃描/搜索而不是一次。

如果更新沒有在WHERE子句中找到匹配項,則更新語句的成本只是查找/掃描。

如果它找到了一個匹配項,並且如果你在前面加上IF EXISTS,它必須找到兩次相同的匹配項。在併發環境中,EXISTS的真實情況可能不再適用於UPDATE。

這正是爲什麼UPDATE/DELETE/INSERT語句允許使用WHERE子句的原因。用它!

相關問題