瀏覽時,我發現following問題/討論關於插入不存在的記錄的「最佳」方法。其中一個令我感到震驚的是[Remus Rusanu]之一,陳述如下:可以INSERT <table>(x)VALUES(@x)WHERE NOT EXISTS(SELECT * FROM <table> WHERE x = @x)會導致重複嗎?
兩種變體都不正確。您將插入一對重複的@ value1,@ value2,保證。
儘管我對INSERT(並且沒有顯式鎖定/事務管理存在)的檢查是'分離'的語法表示同意。我有一個很難理解爲什麼以及何時這將是其他建議語法看起來像這樣
INSERT INTO mytable (x)
SELECT @x WHERE NOT EXISTS (SELECT * FROM mytable WHERE x = @x);
真的,我不想啓動(別人)什麼是最好的/最快的討論,我也不認爲語法可以'替換'一個唯一的索引/約束(或PK),但我真的需要知道在什麼情況下這個構造可能會導致雙打,因爲我過去一直使用這種語法,並想知道繼續這樣做是不是安全的在將來。
我認爲發生的是INSERT & SELECT都在同一個(隱式)事務中。查詢將對相關記錄(鍵)執行一次IX鎖定,並且在整個查詢完成之前不會釋放它,因此只有在記錄插入後。 這個鎖阻止所有其他連接進行相同的INSERT,因爲它們在插入完成後才能自己獲取鎖;只有這樣他們才能獲得鎖定,並且如果記錄已經存在或不存在,將自行開始驗證。
至於恕我直言,找出最好的方法是通過測試,我一直在運行下面的代碼在我的筆記本電腦,同時:
創建見下表
CREATE TABLE t_test (x int NOT NULL PRIMARY KEY (x))
運行在很多很多在並行連接)
SET NOCOUNT ON
WHILE 1 = 1
BEGIN
INSERT t_test (x)
SELECT x = DatePart(ms, CURRENT_TIMESTAMP)
WHERE NOT EXISTS (SELECT *
FROM t_test old
WHERE old.x = DatePart(ms, CURRENT_TIMESTAMP))
END
到目前爲止,唯一需要注意的事情是:
-
個
- 沒有遇到的錯誤(還)
- CPU正在追趕着那個相當火爆=)
- 表中保存300條記錄很快(由於3ms的日期時間的「精確」)沒有實際刀片發生任何更多的,符合市場預期。
UPDATE:
原來我上面的例子是沒有做什麼,我打算做的事。我不是試圖同時插入相同的記錄,而是多次連接,而不是 - 在第一秒之後插入已經存在的記錄。由於它可能需要大約一秒鐘的時間來複制粘貼&在下一個連接上執行查詢,因此永遠不會有重複的危險。我會在剩下的時間裏戴上我的驢耳朵......
無論如何,我已經(使用同一個表)
SET NOCOUNT ON
DECLARE @midnight datetime
SELECT @midnight = Convert(datetime, Convert(varchar, CURRENT_TIMESTAMP, 106), 106)
WHILE 1 = 1
BEGIN
INSERT t_test (x)
SELECT x = DateDiff(ms, @midnight, CURRENT_TIMESTAMP)
WHERE NOT EXISTS (SELECT *
FROM t_test old
WHERE old.x = DateDiff(ms, @midnight, CURRENT_TIMESTAMP))
END
看哪看哪&適應測試更在手頭的事情線,輸出窗口現在持有充足沿着錯誤的的
線消息2627,級別14,狀態1,行8 違反PRIMARY KEY約束 'PK__t_test__3BD019E521C3B7EE' 的。無法在對象'dbo.t_test'中插入>重複鍵。重複鍵值是(57581873)。
FYI:由於Andomar指出,加入HOLDLOCK和/或SERIALIZABLE提示確實「解決了」問題,但隨後被證明是導致大量的死鎖......這是不是很大,但沒有任何意外當我想通了。
想我有相當多的代碼審查的做...
您剛剛在SO中創建了SQL注入,並帶有標題 –
Heheh,等到'我的兒子'報名參加吧! (http://xkcd.com/327/) – deroby
爲了避免死鎖,您可以將'UPDLOCK'添加到混音中。這將序列化訪問'x'上的範圍鎖。 –