我有一个表,sys_QueueJob
存储队列逻辑数据。
我认为更新并返回就足够了...但是,现在我不确定这是否 100% 安全。
我如何确定无论有多少并行请求都不会返回相同的 ID?
UPDATE sys_QueueJob
SET ExecutionStartedOn = GETDATE()
OUTPUT DELETED.Id as Result
WHERE Id = (select top 1 x.Id
from sys_QueueJob x with (rowlock, updlock, readpast)
where x.ExecutionFinishedOn is null
AND (
x.ExecutionStartedOn is null
OR x.ExecutionStartedOn < DATEADD(HOUR, -1, GETDATE())
)
order by x.CreatedOn asc)
排队
就像我在这篇文章中讨论的那样,处理队列的可靠方法是使用如下查询:
当然,对于大多数队列查询来说,更大的挑战是对其建立索引以使工作易于分发。通常,排除已完成工作 (ExecutionFinishedOn) 的过滤索引和过滤/排序元素上的键(ExecutionStartedOn、CreatedOn)就足够了。
就您而言,您要查找尚未开始的项目和一个多小时前开始的项目。将它们分成两个独立查找每个配置的“工作人员”可能更有意义,或者如果没有找到尚未开始的行,则添加一些逻辑来查找一个多小时前开始的行。
比较技术
我使用公用表表达式而不是使用子查询进行更新的原因是为了避免查询计划对表进行多次锁定调用。
看一下这个示例,它使用一个名为dbo.WhatsUpLocks的小帮助器视图来汇总会话持有的锁。我还使用扩展事件来观察更新查询运行时在表和相关对象(索引、默认约束等)上获取和释放的锁。
第一种技术
这是查询计划,其中包含对该表的单个读取引用:
以下是查询执行期间获取和释放的锁:
以下是事务回滚之前保留的锁:
第二种技术
让我们将其与您最初的尝试进行比较,该尝试应用于我之前链接的帖子中的表设置。
这是查询计划,现在有两个对该表的读取引用:
以下是查询执行期间获取和释放的锁:
以下是事务回滚之前保留的锁:
差异
查询执行期间获取和释放的锁有很大不同。公共表表达式中的数量比使用子查询更新中的少得多,这主要是因为查询计划中只有单个读取和更新引用。
在查询回滚之前,剩余的锁只有细微的差别。我的结果中的底行显示了页面上的 IX 锁,而您的结果显示了页面上的 UIX 锁。
这些差异加起来有多少取决于处理队列时的并发性,但您最好以正确的方式进行处理,这样您就不必担心它稍后会崩溃。
最后一点,我不希望您放弃这样的想法:公用表表达式是有魔力的。他们不是。您可以使用派生表或创建的视图(生成要更新的单个结果)获得相同的结果,而无需使用子查询。在这种情况下,我发现公共表表达式使查询更容易理解。就这些。
我相信(测试将证明)使用
将在该事务期间锁定表以进行更新,因此您可以捕获未处理的 ID 并可以继续处理该行 - 而另一个调用将捕获下一行。
如果您阅读https://learn.microsoft.com/en-us/sql/relational-databases/sql-server-transaction-locking-and-row-versioning-guide?view=sql-server-ver16 这将有助于了解如何在 SQL 中管理内容。
在我看来,您正在重新处理设置了 StartTime 且一小时内尚未完成的记录?可能需要调整 where 子句以包含该内容。或者让进程在崩溃时将 ID 更新开始时间处理为 NULL...将自动再次可供选择。
事实上,运行创建、插入和更新(无需提交)并再次运行更新(无需提交),输出 1 然后 2 - 不要忘记在测试后提交两次以关闭打开的事务。