代码之家  ›  专栏  ›  技术社区  ›  8kb

选择/插入一个upsert的版本:是否有高并发性的设计模式?

  •  11
  • 8kb  · 技术社区  · 15 年前

    我要执行upsert的选择/插入版本。以下是现有代码的模板:

    // CREATE TABLE Table (RowID INT NOT NULL IDENTITY(1,1), RowValue VARCHAR(50))
    
    IF NOT EXISTS (SELECT * FROM Table WHERE RowValue = @VALUE)
    BEGIN
       INSERT Table VALUES (@Value)
       SELECT @id = SCOPEIDENTITY()
    END
    ELSE
       SELECT @id = RowID FROM Table WHERE RowValue = @VALUE)
    

    查询将从许多并发会话中调用。我的性能测试表明,在特定的负载下,它将始终抛出主键冲突。

    这个查询是否有一个高并发性的方法,允许它在保持性能的同时仍然避免插入已经存在的数据?

    3 回复  |  直到 8 年前
        1
  •  15
  •   gbn    15 年前

    您可以使用锁使事情可序列化,但这会减少并发性。为什么不先尝试通用条件(“主要插入或主要选择”),然后安全处理“补救”措施?也就是说,“JFDI”模式…

    预计大部分插入物(球形公园70-80%+):

    试着插入。如果失败,则该行已被创建。无需担心并发性,因为Try/Catch为您处理重复项。

    BEGIN TRY
       INSERT Table VALUES (@Value)
       SELECT @id = SCOPEIDENTITY()
    END TRY
    BEGIN CATCH
        IF ERROR_NUMBER() <> 2627
          RAISERROR etc
        ELSE -- only error was a dupe insert so must already have a row to select
          SELECT @id = RowID FROM Table WHERE RowValue = @VALUE
    END CATCH
    

    主要选择:

    类似,但先尝试获取数据。无数据=需要插入。同样,如果两个并发调用试图插入,因为它们都发现行缺少try/catch句柄。

    BEGIN TRY
       SELECT @id = RowID FROM Table WHERE RowValue = @VALUE
       IF @@ROWCOUNT = 0
       BEGIN
           INSERT Table VALUES (@Value)
           SELECT @id = SCOPEIDENTITY()
       END
    END TRY
    BEGIN CATCH
        IF ERROR_NUMBER() <> 2627
          RAISERROR etc
        ELSE
          SELECT @id = RowID FROM Table WHERE RowValue = @VALUE
    END CATCH
    

    第二个看起来是重复的,但它是高度并发的。锁也可以实现相同的效果,但代价是牺牲并发性…

    编辑:

    为什么? 使用合并…

    如果使用output子句,它将只返回更新的内容。所以您需要一个虚拟更新来为output子句生成插入的表。如果您必须使用许多调用(如op所暗示的那样)进行虚拟更新,那么这将是大量的日志写入操作。 只是 以便能够使用合并。

        2
  •  1
  •   Mehul Mistri    12 年前
    // CREATE TABLE Table (RowID INT NOT NULL IDENTITY(1,1), RowValue VARCHAR(50))
    

    --请确保将rowvalue和rowid上的非聚集唯一索引作为聚集索引。

    IF EXISTS (SELECT * FROM Table WHERE RowValue = @VALUE)
       SELECT @id = RowID FROM Table WHERE RowValue = @VALUE
    ELSE BEGIN
       INSERT Table VALUES (@Value)
       SELECT @id = SCOPEIDENTITY()
    END
    
        3
  •  0
  •   pim    8 年前

    和往常一样,GBN的回答是正确的,并最终引导我找到我需要的地方。然而,我发现了一个特殊的边缘案件,他的方法没有涵盖。那是一个 2601 识别一个 Unique Index Violation .

    为了弥补这个不足,我修改了他的代码如下

    ...
    declare @errornumber int = ERROR_NUMBER()
    if @errornumber <> 2627 and @errornumber <> 2601
    ...
    

    希望这能帮助别人!

    推荐文章