【发布时间】:2014-03-02 11:20:45
【问题描述】:
我有一个复杂的实体(我们称之为 Thing),它在 SQL Server 中表示为多个表:一个父表 dbo.Thing 和多个子表 dbo.ThingBodyPart、dbo.ThingThought 等。我们已经实现使用dbo.Thing 上的单个rowversion 列的乐观并发,使用UPDATE OUTPUT INTO technique。这一直很好,直到我们向dbo.Thing 添加了一个触发器。我正在寻求有关选择不同方法的建议,因为我相当确信我目前的方法无法修复。
这是我们当前的代码:
CREATE PROCEDURE dbo.UpdateThing
@id uniqueidentifier,
-- ...
-- ... other parameters describing what to update...
-- ...
@rowVersion binary(8) OUTPUT
AS
BEGIN TRANSACTION;
BEGIN TRY
-- ...
-- ... update lots of Thing's child rows...
-- ...
DECLARE @t TABLE (
[RowVersion] binary(8) NOT NULL
);
UPDATE dbo.Thing
SET ModifiedUtc = sysutcdatetime()
OUTPUT INSERTED.[RowVersion] INTO @t
WHERE
Id = @id
AND [RowVersion] = @rowVersion;
IF @@ROWCOUNT = 0 RAISERROR('Thing has been updated by another user.', 16, 1);
COMMIT;
SELECT @rowVersion = [RowVersion] FROM @t;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0 ROLLBACK;
EXEC usp_Rethrow_Error;
END CATCH
在我们将INSTEAD OF UPDATE 触发器添加到dbo.Thing 之前,这非常有效。现在存储过程不再返回新的@rowVersion 值,而是返回未修改的旧值。我不知所措。是否有其他方法可以像上述方法一样有效和简单,但也可以与触发器一起使用?
为了说明这段代码究竟出了什么问题,请考虑以下测试代码:
DECLARE
@id uniqueidentifier = 'b0442c71-dbcb-4e0c-a178-1a01b9efaf0f',
@oldRowVersion binary(8),
@newRowVersion binary(8),
@expected binary(8);
SELECT @oldRowVersion = [RowVersion]
FROM dbo.Thing
WHERE Id = @id;
PRINT '@oldRowVersion = ' + convert(char(18), @oldRowVersion, 1);
DECLARE @t TABLE (
[RowVersion] binary(8) NOT NULL
);
UPDATE dbo.Thing
SET ModifiedUtc = sysutcdatetime()
OUTPUT INSERTED.[RowVersion] INTO @t
WHERE
Id = @id
AND [RowVersion] = @oldRowVersion;
PRINT '@@ROWCOUNT = ' + convert(varchar(10), @@ROWCOUNT);
SELECT @newRowVersion = [RowVersion] FROM @t;
PRINT '@newRowVersion = ' + convert(char(18), @newRowVersion, 1);
SELECT @expected = [RowVersion]
FROM dbo.Thing
WHERE Id = @id;
PRINT '@expected = ' + convert(char(18), @expected, 1);
IF @newRowVersion = @expected PRINT 'Pass!'
ELSE PRINT 'Fail. :('
当触发器不存在时,此代码正确输出:
@oldRowVersion = 0x0000000000016CDC
(1 row(s) affected)
@@ROWCOUNT = 1
@newRowVersion = 0x000000000004E9D1
@expected = 0x000000000004E9D1
Pass!
当触发器存在时,我们没有收到预期值:
@oldRowVersion = 0x0000000000016CDC
(1 row(s) affected)
(1 row(s) affected)
@@ROWCOUNT = 1
@newRowVersion = 0x0000000000016CDC
@expected = 0x000000000004E9D1
Fail. :(
对于不同的方法有什么想法吗?
我假设 UPDATE 是一个原子操作,它是,除非有触发器,但显然不是。我错了吗?在我看来,这似乎真的很糟糕,每个语句背后都潜藏着潜在的并发错误。如果触发器真的是INSTEAD OF,我不应该取回正确的时间戳,就好像触发器的UPDATE 是我实际执行的那样吗?这是 SQL Server 错误吗?
【问题讨论】:
标签: sql-server tsql stored-procedures triggers optimistic-locking