當您需要根據某些條件執行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
什麼插入或刪除?
當您需要根據某些條件執行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
什麼插入或刪除?
我不能完全肯定,但我得到的印象是,這個問題實際上是關於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之前,您必須使用一個尷尬的三階段模型,在設定的水平(仍高於行由行更好)來處理這個:
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
影響的行轉換爲表變量,如果您需要事後瞭解所做的事情。簡單,快速,無風險。做到這一點。
感謝您向我介紹MERGE。這真的很酷。 – jwarzech
MERGE非常酷 - 想指出,因爲我剛剛發現了這一點:MERGE不會避免競爭條件,除非添加適當的提示('WITH(HOLDLOCK)')。參見[Dan Guzman關於用MERGE指出UPSERT競爭狀態的工作](http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx) –
+ 1代表「開發人員變身的DBA經常天真地將它逐行寫入」! –
的IF EXISTS
語句的性能:
IF EXISTS(SELECT 1 FROM mytable WHERE someColumn = someValue)
取決於當前滿足查詢索引。
你不應該在大多數情況下這樣做。根據您的交易級別,您創建了競爭條件,現在在您的示例中,這並不重要,但數據可以從第一次選擇更改爲更新。而你所做的一切都是強迫SQL去做更多的工作
要知道肯定的最好方法是測試兩個差異,看看哪一個能給你適當的性能。
有輕微的影響,因爲你在做相同的檢查兩次,至少在你的榜樣:
IF EXISTS(SELECT 1 FROM Contacs WHERE [Type] = 1)
具有查詢,看看是否有任何,如果爲true:
UPDATE Contacs SET [Deleted] = 1 WHERE [Type] = 1
必須查詢,查看哪些...沒有理由的相同檢查兩次。現在,如果你正在尋找的條件被索引,它應該是快速的,但對於大型表格,你可能會看到一些延遲,因爲你正在運行select。
是的,這會影響性能(影響性能的程度會受到很多因素的影響)。實際上你正在做兩次相同的查詢(在你的例子中)。問問你自己,你是否需要在你的查詢中保持這種防禦性,以及在哪些情況下行不在那裏?另外,對於更新語句,受影響的行可能是確定是否更新任何內容的更好方法。
IF EXISTS
基本上會做一個SELECT - 與UPDATE相同。
因此,它會降低性能 - 如果沒有什麼更新,你沒工作的相同數量(UPDATE會質疑同樣缺乏行作爲你的選擇),如果有什麼更新,你juet做一個不需要的選擇。
另一種方法是使用burnall的答案:只要先進行更新,然後檢查@@ rowcount。如果它是零,那麼沒有任何更新,你打電話通過插入。 –
'IF EXISTS'是**不是**插入或更新操作的好設計。它有一個競爭條件,即使在'BEGIN TRAN' /'COMMIT TRAN'塊內。 – Aaronaught
@Aarounaught - 好點。刪除了那一塊。 – DVK
這對一次更新/刪除/插入沒有用。
如果有條件後有多個運算符,可能會增加性能。
在最後一種情況下寫的更好
update a set .. where ..
if @@rowcount > 0
begin
..
end
+1。我必須承認,在一讀這個問題時,至少對我而言這並不明顯,這是OP所指的。必須遲到... –
正是。做什麼*通常*是需要的行動,檢查結果,然後在必要時做備用案例。 –
+1我還要走這條路,但他的問題並不清楚......好的回答 – JoshBerke
爲UPDATE
和DELETE
你不應該這樣做,因爲如果對性能的影響,它是不是一個積極之一。
對於INSERT
可能有情況下您INSERT
將引發一個異常(UNIQUE CONSTRAINT
違規等),在這種情況下,你可能想阻止它與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>
無論
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作爲下一個可用唯一值,則不安全。
如果您使用的是MySQL,那麼您可以使用insert ... on duplicate。
IF EXISTS....UPDATE
不要這樣做。它強制兩次掃描/搜索而不是一次。
如果更新沒有在WHERE子句中找到匹配項,則更新語句的成本只是查找/掃描。
如果它找到了一個匹配項,並且如果你在前面加上IF EXISTS,它必須找到兩次相同的匹配項。在併發環境中,EXISTS的真實情況可能不再適用於UPDATE。
這正是爲什麼UPDATE/DELETE/INSERT語句允許使用WHERE子句的原因。用它!
爲什麼不看執行計劃? – RichardOD