2013-03-13 153 views
5

我運行值選擇我的臨時表,並將其插入到數據庫中,像這樣一個存儲過程:INSERT INTO ..選擇..違反唯一約束

INSERT INTO emails (EmailAddress) (
    SELECT 
     DISTINCT eit.EmailAddress 
    FROM #EmailInfoTemp eit 
     LEFT JOIN emails ea 
     ON eit.EmailAddress = ea.EmailAddress 
    WHERE ea.EmailAddressID IS NULL ) 

在罕見的情況下(〜每對夫婦一次在處理數千個請求的服務器上花費數小時),然後我在EmailAddress列的索引上收到唯一約束錯誤「違反UNIQUE KEY約束」。

我可以確認我沒有傳入重複值。即使我是,它應該被DISTINCT抓住。

- SQL Server 2008的 -Stored PROC +不使用事務+ JDBC的CallableStatement

是可能發生的SELECT和隨後的INSERT之間,不存在對相同/不同的存儲過程已完成的INSERT另一個電話與類似的數據?如果是這樣,那麼防止這種情況的最好方法是什麼?

一些想法:我們有許多重複的「客戶端」實例在生產環境中同時與這個SQL Server進行通信,所以我的第一反應是併發問題,但我似乎無法自己複製它。這是我的最佳猜測,但到目前爲止它已經走到了盡頭。這種情況在我們的暫存環境中不會發生,與生產環境相比,負載無關緊要。這是我開始關注併發問題的主要原因。

+1

雙方EmailAddress中的NULL值可能違反唯一鍵約束。如果此列可以爲空,則可以將插入重寫爲「not exists」。 – 2013-03-13 15:00:05

+0

好點。我檢查代碼中的空值,但我想我可以在sql中再次檢查它。 – 2013-03-13 15:06:39

回答

5

該錯誤可能是由兩個會話同時執行插入操作引起的。

您可以使用MERGE使您的SQL代碼更安全。正如Aaron Bertrand的評論所說(謝謝!),you have to include a with (holdlock) hint to make merge really safe

; merge emails e with (holdlock) 
using #EmailInfoTemp eit 
on  e.EmailAddress = eit.EmailAddress 
when not matched then insert 
     (EmailAddress) values (eit.EmailAddress) 

merge聲明將採取適當的鎖,以確保沒有其他會話可以潛入之間它的「不匹配」檢查和「插入」。

如果您不能使用merge,則可以解決客戶端問題。確保沒有兩個插件同時運行。這通常易於使用mutex或其他同步構造。

+1

['MERGE'不一定安全,除非您添加'HOLDLOCK'提示](http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx )。 – 2013-03-13 14:53:44

+0

@AaronBertrand:謝謝。 MERGE的MSDN頁面顯示「指定READPAST時,如果[按目標]未匹配,那麼INSERT可能會導致違反UNIQUE約束的INSERT操作。」所以我認爲它是安全的,除非你指定'readpast'。 – Andomar 2013-03-13 14:56:50

+1

是的,微軟通常不會記錄所有可能中斷的情況。該文檔也沒有提到[任何這些尚未解決的錯誤(滾動到底部)](http://www.sqlperformance.com/2013/02/t-sql-queries/another-合併-BUG)。 :-) – 2013-03-13 14:59:14