在试图修复由于并发冲突导致的数据错误时,我意识到我不完全确定SQL Server中乐观并发的工作方式。假设READ_COMMITTED隔离级别。一个类似的例子:
BEGIN TRAN
SELECT * INTO #rows FROM SourceTable s WHERE s.New = 1
UPDATE d SET Property = 'HelloWorld' FROM DestinationTable d INNER JOIN #rows r ON r.Key = d.Key
UPDATE s SET Version = GenerateRandomVersion() FROM SourceTable s
INNER JOIN #rows r on r.Key = s.Key AND r.Version = s.Version
IF @@ROWCOUNT <> SELECT COUNT(*) FROM #rows
RAISEERROR
END IF
COMMIT TRAN这完全是原子/线程安全吗?
UPDATE s上的ON子句应通过版本和ROWCOUNT检查防止并发更新。但这真的是真的吗?下面类似的查询呢?
BEGIN TRAN
SELECT * INTO #rows FROM SourceTable s WHERE s.New = 1
UPDATE s SET New = 0 AND Version = GenerateRandomVersion() FROM SourceTable s
INNER JOIN #rows r on r.Key = s.Key AND r.Version = s.Version
IF @@ROWCOUNT <> SELECT COUNT(*) FROM #rows
RAISEERROR
END IF
UPDATE d SET Property = 'HelloWorld' FROM DestinationTable d INNER JOIN #rows r ON r.Key = d.Key
COMMIT TRAN我在这里担心的是,上述脚本的并发执行将到达UPDATE的语句,得到一个@@ROWCOUNT,该@ROWCOUNT是瞬态的/尚未实际提交给DB,因此这两个线程/执行都将继续通过IF语句,并执行重要的UPDATE d语句,在这种情况下,这个语句是幂等的,但在我最初的生产用例中不是这样。
发布于 2022-03-18 14:09:46
我认为您想要做的是删除脚本中非常小的竞赛条件,使其尽可能基于设置。
BEGIN TRAN
DECLARE @UpdatedSources Table (Key INT NOT NULL);
UPDATE s SET New = 0
FROM SourceTable s
WHERE s.New = 1
OUTPUT Inserted.Key into @UpdatedSources
UPDATE d SET Property = 'HelloWorld'
FROM DestinationTable d
INNER JOIN @UpdatedSources r ON r.Key = d.Key
COMMIT TRAN我认为表中的“version”列让人感到困惑--您试图在表中构建原子性,而不仅仅是让DB事务处理它。使用上面的脚本,将锁定New=1的行,直到事务提交,因此后续的尝试只会找到“实际”的新行或new=0的行。
评论后的更新
要演示表上的锁定,如果您希望看到它,则可以尝试启动死锁。如果要与第一个查询同时运行此查询,我认为您最终可能会陷入死锁,但取决于这些查询的运行速度,您可能很难看到它:
BEGIN TRAN
SELECT *
FROM DestinationTable d
INNER JOIN SourceTable ON r.Key = d.Key
WHERE s.New = 1
UPDATE s SET New = 0
FROM SourceTable s
WHERE s.New = 1
COMMIT TRANhttps://stackoverflow.com/questions/71527263
复制相似问题