【问题标题】:Danger of using 'IF EXISTS... UPDATE .. ELSE .. INSERT' and what is the alternative?使用 'IF EXISTS... UPDATE .. ELSE .. INSERT' 的危险,还有什么替代方法?
【发布时间】:2013-06-07 01:20:25
【问题描述】:

此问题与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 (...)

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

【问题讨论】:

标签: sql sql-server-2008


【解决方案1】:

使用MERGE

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

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

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

    • 错了,MERGE也需要锁来保证原子性。见:weblogs.sqlteam.com/dang/archive/2009/01/31/…
    • @SørenBoisen 你能指出我帖子中我说 MERGE 不需要锁的确切位置吗?
    • @RemusRusanu 就是这样。您只需声明 MERGE 将确保适当的原子性,而没有提及它绝对需要锁定提示来执行此操作。
    【解决方案3】:

    Merge 在第一种情况下是为了比较 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 是关于并发的。

    【讨论】:

    • 这将在并发下失败。使用 MERGE。
    • 编辑了我的帖子,谢谢。最初我也教过使用 Merge,只是先检查了其他选项
    • 如果您不使用 HOLDLOCK 提示,MERGE 在并发下也会失败:weblogs.sqlteam.com/dang/archive/2009/01/31/…
    【解决方案4】:

    本例中的示例 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');
    

    【讨论】:

      猜你喜欢
      • 2012-09-08
      • 2014-04-27
      • 1970-01-01
      • 1970-01-01
      • 2012-08-08
      • 1970-01-01
      • 2017-09-09
      • 2013-11-26
      相关资源
      最近更新 更多