我有一个简单的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声明,我就越认为它只是这个任务的错误工具。它似乎没有任何支持这种工作方式,我在网上找到的解决方案是部分工作方法而不是正确的方法。