这是这个问题的第2部分: Select rows and Update same rows for locking?
我的数据库中有一个包含数百万条记录的表。我已经创建了一个存储过程来提取X量的记录并将它们标记为已被锁定,因此当我的应用程序调用下一组X量时,它将仅拉取未锁定的记录,依此类推...等等... 我发现有数百万条记录,效率不高。查询需要一段时间才能运行。有没有人有一些建议来提高我的查询效率,同时保持锁定记录的相同想法?以下是存储过程:
Set Rowcount @topCount
SELECT *
into #temp_candidates
FROM dbo.Candidates
Where isTested = 0 and
isLocked = 0 and
validated = 0 and
validationId is null
UPDATE dbo.Candidates
SET islocked = 1,
validationId = @validationId,
validationDateTime = @validationDateTime
WHERE id IN (SELECT id FROM #temp_candidates)
Select *
from dbo.Candidates
where id in (SELECT id FROM #temp_candidates) IF EXISTS (SELECT NULL FROM
tempdb.dbo.sysobjects WHERE ID = OBJECT_ID(N'tempdb..#temp_candidates'))
BEGIN
DROP TABLE #temp_candidates
END
END
答案 0 :(得分:2)
这种程序在并发下保证失败。你想要实现的是使用一个表作为队列,这是众所周知的容易出错,并且有很多已知的经过测试和尝试的方法来使用Use Tables as Queues。现在你正走在一条无处可走的道路上。关于你之前的问题,你应该选择Cade的答案。
要从表中对项目进行出列(锁定它们以便在存在并发线程的情况下尝试执行相同操作)必须满足几个条件,所有这些都在我链接的文章中列出,而实际查询只是其中之一。 大多数重要条件是实际拥有正确的聚簇索引。 isLocked
字段必须是群集密钥中最左边的字段,后跟排序条件。
CREATE CLUSTERED INDEX cdxCandidates ON Candidates(isLocked, ...);
此聚簇索引是高效出列操作所必需的。然后你必须使用OUTPUT子句,这是正确拉出它的唯一方法:
WITH cte AS (
SELECT TOP (@x) isLocked, ...
FROM Candidates WITH (READPAST)
WHERE isLocked = 0
ORDER BY ...)
UPDATE cte
SET isLocked = 1
OUTPUT INSERTED.*;
查询处理器了解您正在选择更新,并将以阻止并发线程获取相同行的方式获取表中的行。
顺便提一下,这个'方案'正好是Service Broker的QUEUE使用的方案(聚簇索引条件和在READPAST SELECT之上的OUTPUT更新),它是专门为高并发入队/出队操作而设计的。其他替代方案被认为是唯一一个在保持正确性的同时获得任何表现的机会
答案 1 :(得分:1)
我不确定我是否理解临时数据库的必要性。为什么不设置isLocked = 1表示您希望的任何记录(在事务内)。做你想做的任何事。然后设置isLocked = 1,其中isLocked = 0并重复。