我的团队正在努力解决有关并发线程/进程的奇怪的Microsoft SQL Server问题。很少,在两个不同的线程上UPDATE和同时SELECT
之间似乎存在竞争条件。很难再现,因为时机至关重要。
发生冲突时,SELECT
不会返回所需的记录,但不会生成错误。相反,SELECT
表现得好像记录不存在,这是不正确的。结果我们的服务变得混乱,并且在我们恢复之前会有一些混乱。
为了消除我们服务中出现错误的可能性,我使用普通的C#代码和System.Data.SqlClient
重现了这个问题。我很乐意根据要求提供该代码。我试图将代码和表格减少到重现问题所需的最低限度。
如下所示创建测试表:
CREATE TABLE [TestTable]
(
[RecordId] int IDENTITY(1,1) UNIQUE,
[Locked] int NOT NULL DEFAULT 0,
[Priority] int NULL,
[Status] int NULL,
[SystemName] nvarchar(50) NULL
)
该表有一个像这样创建的索引:
CREATE NONCLUSTERED INDEX [IDX_GENERAL]
ON [TestTable] ([Status], [SystemName], [Locked])
INCLUDE ([RecordId], [Priority])
该表包含如下所示的单个记录:
INSERT INTO [TestTable] ([Locked], [Priority], [Status], [SystemName])
VALUES (0, 3, 3000, 'System1')
在我们的服务中,多个线程和进程通过原子设置他们的“锁定”来获取对记录的独占写入权限。领域到一个。然后,在进行任何必要的修改之后,线程/进程必须通过重置其“锁定”记录来释放记录。列为零。我看到的是一个线程解锁记录时非常罕见的情况,同时另一个线程试图找到它:
一个线程执行这样的UPDATE(成功):
UPDATE [TestTable]
SET [Locked] = 0
OUTPUT INSERTED.*
WHERE [Locked] = 1
AND [RecordId] = 1
AND [Status] = 3000
AND [SystemName] = 'System1'
同时,另一个线程像这样执行SELECT
(返回空):
SELECT [RecordId]
FROM [TestTable]
WHERE [Status] = 3000
AND [SystemName] = 'System1'
ORDER BY Priority DESC, RecordId ASC
我认为索引是问题的一部分,因为如果我删除了Status
或SystemName
键,那么我就无法重现该问题。我不知道会导致这种行为的原因。我读过的所有内容都说这根本不可能发生。
我欢迎任何有关如何排除故障的问题,想法或建议......
答案 0 :(得分:0)
你的逻辑存在缺陷。在开始时,许多线程尝试获取锁的记录的id。
SELECT [RecordId]
FROM [TestTable]
WHERE [Locked]=0 AND [Status]=3000 AND [SystemName]='System1'
ORDER BY Priority DESC, RecordId ASC
然后他们尝试设置锁定
UPDATE [TestTable]
SET [Locked]=1
OUTPUT INSERTED.*
WHERE [Locked]=0 AND [RecordId]={0}
AND [Status]=3000 AND [SystemName]='System1'
当然可能会发生
竞争条件是程序结果发生的错误 取决于两个或多个线程中的哪一个到达特定块 代码第一。多次运行程序会产生不同的结果, 并且无法预测任何给定运行的结果。
在你的情况下,这不是一个错误,而是一些罕见的逻辑案例 你有两个选择