我知道至少有三种方法可以插入记录,如果它不存在于表格中:
第一个是使用,如果不存在:
IF NOT EXISTS(select 1 from table where <condition>)
INSERT...VALUES
第二个是使用合并:
MERGE table AS target
USING (SELECT values) AS source
ON (condition)
WHEN NOT MATCHED THEN
INSERT ... VALUES ...
第三个是使用insert ... select:
INSERT INTO table (<values list>)
SELECT <values list>
WHERE NOT EXISTS(select 1 from table where <condition>)
但哪一个最好?
第一个选项似乎不是线程安全的,因为如果两个或多个用户尝试插入相同的记录,则可能会在if和下面的insert语句中的select语句之间插入记录。
至于第二个选项,合并似乎对此有些过分,因为documentation表示:
性能提示:当两个表具有复杂的匹配特征混合时,MERGE语句描述的条件行为最有效。例如,如果行不存在则插入行,或者如果行匹配则更新行。当简单地基于另一个表的行更新一个表时,可以使用基本的INSERT,UPDATE和DELETE语句实现改进的性能和可伸缩性。
所以我认为第三种选择对于这种情况是最好的(如果它不存在则只插入记录,如果没有则需要更新),但我想知道SQL Server专家的想法
请注意,在插入之后,我不想知道唱片是否已存在或是否是一张全新的唱片,我只需要它就在那里以便我可以继续其余的存储过程。
答案 0 :(得分:0)
当您需要保证在无法用UNIQUE或PRIMARY KEY约束表达的条件下记录的唯一性时,您确实需要确保在一个事务中完成对存在和插入的检查。您可以通过以下任一方式实现此目的:
第四种方法可以帮助您更好地构建代码,并使其在您需要一次处理一批记录的情况下工作。您可以创建TABLE变量或临时表,插入需要插入的所有记录,然后根据此变量编写INSERT,UPDATE和DELETE语句。
下面是演示此方法的(伪)代码:
-- Logic to create the data to be inserted if necessary
DECLARE @toInsert TABLE (idCol INT PRIMARY KEY,dataCol VARCHAR(MAX))
INSERT INTO @toInsert (idCol,dataCol) VALUES (1,'row 1'),(2,'row 2'),(3,'row 3')
-- Logic to insert the data
INSERT INTO realTable (idCol,dataCol)
SELECT TI.*
FROM @toInsert TI
WHERE NOT EXISTS (SELECT 1 FROM realTable RT WHERE RT.dataCol=TI.dataCol)
在许多情况下,我使用这种方法,因为它使TSQL代码更容易阅读,可以重构和应用单元测试。
答案 1 :(得分:0)
在弗拉基米尔·巴拉诺夫的comment之后,阅读Dan Guzman关于Conditional INSERT/UPDATE Race Condition和“UPSERT” Race Condition With MERGE的博客文章,似乎这三个选项在多用户环境中都有同样的缺点。
取消合并选项作为矫枉过正,我们留下选项1和3。
Dan提出的解决方案是使用显式事务并向select添加锁定提示以避免竞争条件。
这样,选项1变为:
BEGIN TRANSACTION
IF NOT EXISTS(select 1 from table WITH (UPDLOCK, HOLDLOCK) where <condition>)
BEGIN
INSERT...VALUES
END
COMMIT TRANSACTION
和选项2变为:
BEGIN TRANSACTION
INSERT INTO table (<values list>)
SELECT <values list>
WHERE NOT EXISTS(select 1 from table WITH (UPDLOCK, HOLDLOCK)where <condition>)
COMMIT TRANSACTION
当然,在这两个选项中都需要进行一些错误处理 - 每个事务都应该使用try ... catch,这样我们就可以在发生错误的情况下回滚事务。
话虽如此,我认为第三种选择可能是我个人的最爱,但我不认为应该有所不同。
在我对Aaron Bertrand的conversation之后的其他问题的评论中 - 我并不完全相信使用ISOLATION LEVEL
比单个查询提示更好的解决方案,但至少这是另一个需要考虑的选择:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
INSERT INTO table (<values list>)
SELECT <values list>
WHERE NOT EXISTS(select 1 from table where <condition>);
COMMIT TRANSACTION;