使用'如果存在...更新...... ELSE ..插入'的危险和替代方案是什么?

时间:2013-06-11 08:47:10

标签: sql sql-server-2008

此问题与Occasionally Getting SqlException: Timeout expired有关。实际上,我在我的应用中大量使用IF EXISTS... UPDATE .. ELSE .. INSERT。但是用户Remus Rusanu说你不应该使用它。为什么我不应该使用它以及它包含的危险。所以,如果我有

IF EXISTS (SELECT * FROM Table1 WHERE Column1='SomeValue')
    UPDATE Table1 SET (...) WHERE Column1='SomeValue'
ELSE
    INSERT INTO Table1 VALUES (...)

如何重写此语句以使其有效?

4 个答案:

答案 0 :(得分:3)

使用MERGE

您的SQL失败,因为在INSERT发生之前,2个并发重叠和非常接近的调用都会从EXISTS中获得“false”。所以他们都试图插入,当然一个失败。

这里有更多解释:Select / Insert version of an Upsert: is there a design pattern for high concurrency?这个答案虽旧,但在添加MERGE之前适用

答案 1 :(得分:2)

IF EXISTS ... UPDATE ...(和IF NOT EXISTS ... INSERT ...)的问题在于,在并发下,多个线程(事务)将执行IF EXISTS部分,而都会得出相同的结论 (例如,它不存在)并尝试采取相应行动。结果是所有线程都尝试INSERT导致密钥违规。根据代码,这可能导致约束违规错误,死锁,超时或更糟(更新丢失)。

您需要确保检查IF EXISTS和操作是原子的。在SQL Server 2008之前,解决方案涉及使用事务和锁定提示,并且非常容易出错(容易出错)。发布SQL Server 2008后,您可以使用MERGE,这将确保正确的原子性,因为单个语句和引擎了解您要尝试的操作。

答案 2 :(得分:1)

合并,是在第一种情况下创建来比较2个表,所以如果是这种情况你可以使用合并。

请看下面的内容,这也是另一种选择。

在这种情况下,遗憾的是您可能会遇到并发问题。

--Just update a row 
UPDATE Table1 SET (...) WHERE Column1='SomeValue'
   -- If 0 rows are updated ...
  IF @@ROWCOUNT=0
--Insert Row
INSERT INTO Table1 VALUES (...)

this博客中,它解释了更多。

另外this interesting blog是关于并发的。

答案 3 :(得分:1)

本例中的MERGE语句示例为:

MERGE INTO Table1 t1
USING (SELECT 'SomeValue' as Column_id FROM dual) t2 ON
(t1.column_id = t2.column_id)
WHEN MATCHED THEN
    UPDATE SET(...)
WHEN NOT MATCHED THEN
    INSERT (t1.column_id)
    VALUES ('SomeValue');