基于ROWVERSION的MERGE语句的乐观并发

时间:2017-11-08 15:51:11

标签: sql-server tsql

我有一个简单的MERGE语句,我想为其添加乐观并发。

DECLARE @Id         INT,
        @IsEnabled  BIT,
        @RowVersion ROWVERSION;

MERGE [dbo].Subscription WITH (HOLDLOCK) AS [Target]
USING
( SELECT @Id, @IsEnabled )
AS [Source]
( Id, IsEnabled )
ON
( [Target].Id = [Source].Id )
WHEN MATCHED THEN
    UPDATE SET IsEnabled = [Source].IsEnabled
WHEN NOT MATCHED THEN
    INSERT ( IsEnabled ) VALUES ( [Source].IsEnabled );

IF @Id IS NULL
BEGIN
    SELECT @Id = CAST(SCOPE_IDENTITY() AS INT);
END;

SELECT @Id AS Id;

我们使用ROWVERSION来提供更改跟踪。

当RowVersion值不匹配时,不应该发生任何更改,我们应该返回一个标志,指示并发检查失败,因此可以要求用户重试。

我找到一个suggested approach执行UPDATE,在检测到不同的ROWVERSION时为其设置了现有值。这满足MERGE希望至少更新列,而不实际更改值。

DECLARE @Id         INT,
        @IsEnabled  BIT,
        @RowVersion ROWVERSION;

DECLARE @Result TABLE (WasChanged BIT, Id INT);

MERGE [dbo].Subscription WITH (HOLDLOCK) AS [Target]
USING
( SELECT @Id, @IsEnabled, @RowVersion )
AS [Source]
( Id, IsEnabled, RowVersion )
ON
( [Target].Id = [Source].Id )
WHEN MATCHED THEN
    UPDATE SET IsEnabled = CASE WHEN [Source].Rowversion = [Target].Rowversion          
                                THEN [Source].IsEnabled ELSE [Target].IsEnabled
                           END
WHEN NOT MATCHED THEN
    INSERT ( IsEnabled ) VALUES ( [Source].IsEnabled )
OUTPUT  CASE WHEN [Source].[Rowversion] = INSERTED.[Rowversion]
            THEN 0 ELSE 1
        END AS ConcurrencyCheckFailed, INSERTED.Id AS Id
INTO    @Result;

SELECT ConcurrencyCheckFailed, Id FROM @Result;

这最初似乎是一种可行的解决方法,但执行UPDATE,即使实际上没有更改任何值的MERGE,仍然会导致该行ROWVERSION无缘无故地增加。

更改ROWVERSION会导致并发检查失败,因此用户必须重试。而且由于改变是没有充分理由的,因此这些失败是非常不可取的。

我是否采取了错误的方法试图让MERGE按照我想要的方式工作?

或者,是否有一种使用基于ROWVERSION的乐观并发的{{1}}的好方法?

我越是看MERGE声明,我就越认为它只是这个任务的错误工具。它似乎没有任何支持这种工作方式,我在网上找到的解决方案是部分工作方法而不是正确的方法。

0 个答案:

没有答案