2010-08-04 63 views
62

我一直用類似下面的東西來實現它:只有插入行,如果它尚不存在

INSERT INTO TheTable 
SELECT 
    @primaryKey, 
    @value1, 
    @value2 
WHERE 
    NOT EXISTS 
    (SELECT 
     NULL 
    FROM 
     TheTable 
    WHERE 
     PrimaryKey = @primaryKey) 

...但一旦負載下,發生了主鍵衝突。這是根本插入這張表的唯一聲明。那麼這是否意味着上述說法不是原子的?

問題是,這幾乎不可能隨意重新創建。

也許我可以將其更改爲類似以下內容:

INSERT INTO TheTable 
WITH 
    (HOLDLOCK, 
    UPDLOCK, 
    ROWLOCK) 
SELECT 
    @primaryKey, 
    @value1, 
    @value2 
WHERE 
    NOT EXISTS 
    (SELECT 
     NULL 
    FROM 
     TheTable 
    WITH 
     (HOLDLOCK, 
     UPDLOCK, 
     ROWLOCK) 
    WHERE 
     PrimaryKey = @primaryKey) 

雖然,也許我用錯了鎖或使用太多的鎖什麼的。我只是在一個單一的SQL語句(也許是不正確的)的假設下,如果我的答案是「IF(SELECT COUNT(*)... INSERT」將原子。

沒有人有任何想法?

+3

您是否嘗試過使用沒有'WHEN MATCHED'子句的合併? – 2010-08-04 16:59:18

+3

你在什麼版本的SQL Server? – 2010-08-04 16:59:21

+0

這取決於客戶端。包括2000和2008 R2之間的任何內容。雖然我們可能在聲明最初編寫時已經有7次了! – Adam 2010-08-04 17:20:00

回答

51

"JFDI"模式呢?

BEGIN TRY 
    INSERT etc 
END TRY 
BEGIN CATCH 
    IF ERROR_NUMBER() <> 2627 
     RAISERROR etc 
END CATCH 

說真的,這是最快和最沒有鎖的併發,特別是在大容量時。 如果UPDLOCK升級並且整個表被鎖定,該怎麼辦?

Read lesson 4

第4課:在制定與調整前的索引的更新插入PROC,我第一次相信,​​線將火任何項目,將禁止重複。納達。在短時間內有成千上萬的副本,因爲相同的項目會在同一毫秒內觸發upsert,並且兩個事務都會看到不存在並執行插入操作。經過多次測試,解決方案是使用唯一索引,捕獲錯誤,然後重試允許事務查看行並執行更新,而不是插入。

+0

謝謝 - 好的,我同意這可能是我最終會用到的,並且是實際問題的答案。 – Adam 2010-08-04 19:58:02

+1

我知道依靠這樣的錯誤是不好的,但是我想知道如果只用一個簡單的'INSERT'(不帶'EXISTS')來執行這個操作會更好(即不管插入什麼,只需忽略錯誤2627)。 – Adam 2010-08-04 20:02:40

+0

這取決於您是否大多數插入不存在的值或大部分值* *存在的值。在後一種情況下,我認爲由於大量的例外情況被提出並被忽略,表現會更差。 – GSerg 2010-08-04 21:11:38

1

我不知道這是「官方」的方式,但你可以嘗試的INSERT,並回落到UPDATE如果失敗。

21

我加了HOLDLOCK,原本不存在,請忽略版本沒有這個提示。

就我而言,這已經足夠了:

INSERT INTO TheTable 
SELECT 
    @primaryKey, 
    @value1, 
    @value2 
WHERE 
    NOT EXISTS 
    (SELECT 0 
    FROM TheTable WITH (UPDLOCK, HOLDLOCK) 
    WHERE PrimaryKey = @primaryKey) 

另外,如果你真的想更新行,如果它存在,然後插入如果沒有,你可能會發現this question有用。

+1

當行不存在時,你在鎖定什麼? – 2010-08-04 17:04:04

+2

索引中的相關範圍(本例中爲主鍵)。 – GSerg 2010-08-04 17:05:29

+0

@GSerg同意。 select語句的悲觀/樂觀鎖定需要一個指令。 – DaveWilliamson 2010-08-04 17:06:07

-3

我在過去使用不同的方法完成了類似的操作。首先,我聲明一個變量來保存主鍵。然後,我使用select語句的輸出填充該變量,該語句用這些值查找記錄。然後我做和IF聲明。如果主鍵爲空,則插入,否則返回一些錯誤代碼。

 DECLARE @existing varchar(10) 
    SET @existing = (SELECT primaryKey FROM TABLE WHERE param1field = @param1 AND param2field = @param2) 

    IF @existing is not null 
    BEGIN 
    INSERT INTO Table(param1Field, param2Field) VALUES(param1, param2) 
    END 
    ELSE 
    Return 0 
END 
+0

爲什麼不只是做: IF NOT EXISTS(SELECT * FROM表,其中param1field = @參數1和param2field = @參數2) BEGIN INSERT INTO表(param1Field,param2Field)VALUES(參數1,參數2) END – 2010-08-04 17:29:21

+0

是的,但是看起來它對併發性問題是開放的(例如,如果在select和insert之間的另一個連接上發生了什麼?) – Adam 2010-08-04 17:31:36

+2

@Adam Marc的代碼對於避免鎖定問題並不是更好。處理併發問題的唯一兩種方法是使用WITH(UPDLOCK,HOLDLOCK)進行鎖定或處理插入錯誤並將其轉換爲更新。 – ErikE 2010-10-02 01:06:07

15

你可以用MERGE:

MERGE INTO Target 
USING (VALUES (@primaryKey, @value1, @value2)) Source (key, value1, value2) 
ON Target.key = Source.key 
WHEN MATCHED THEN 
    UPDATE SET value1 = Source.value1, value2 = Source.value2 
WHEN NOT MATCHED BY TARGET THEN 
    INSERT (Name, ReasonType) VALUES (@primaryKey, @value1, @value2) 
+0

在這種情況下,您可以刪除'WHEN MATCHED THEN',因爲Adam只需要在缺失時插入,而不是插入。 – Iain 2011-04-21 16:22:19

+3

對不起,但是沒有在合併語句中添加鎖定提示,您將會得到OP關心的確切問題。 – EBarr 2011-06-11 21:53:49

+7

查看[這篇文章](http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx)更多關於@ EBarr的點 – 2011-12-29 16:03:45

0

首先,巨大的喊出我們的人@gbn他對社會的貢獻。甚至無法解釋我多久聽到他的建議後才發現自己。

無論如何,足夠fanboy ing。

稍微增加他的回答,也許「增強」它。對於像我這樣的人,在<> 2627的情況下,不知道該怎麼辦(沒有空的CATCH不是一個選項)。我從technet發現了這個小塊。

BEGIN TRY 
     INSERT etc 
    END TRY 
    BEGIN CATCH 
     IF ERROR_NUMBER() <> 2627 
      BEGIN 
       DECLARE @ErrorMessage NVARCHAR(4000); 
       DECLARE @ErrorSeverity INT; 
       DECLARE @ErrorState INT; 

       SELECT @ErrorMessage = ERROR_MESSAGE(), 
       @ErrorSeverity = ERROR_SEVERITY(), 
       @ErrorState = ERROR_STATE(); 

        RAISERROR (
         @ErrorMessage, 
         @ErrorSeverity, 
         @ErrorState 
        ); 
      END 
    END CATCH 
相關問題