如何实现以下事务锁定?
在大简化中 - 我有一个带有状态(创建,已启动,已完成)的“任务”表。我想创建存储过程GetNext
以获取尚未启动的前1个任务(具有Created
状态)。
在此过程中,我想将任务标记为Started
。显然,当两个进程调用此过程并获得相同的任务时,我希望避免这种情况。
不会经常调用该过程,因此性能不是问题,保持数据不被破坏是一个问题。
所以我想做这样的事情:
UPDATE tblTasks
SET Status = 'Started'
WHERE TaskId = (SELECT TOP 1 TaskId
FROM tblTasks
WHERE Status = 'Created')
我也希望收到我刚刚更新的任务,而不是上面我需要的东西:
DECLARE @TaskId AS INT = (SELECT TOP 1 TaskId FROM tblTasks WHERE Status = 'Created')
UPDATE tblTasks
SET Status = 'Started'
WHERE TaskId = @TaskId
[... - Do something with @TaskId - not relevant]
OR
DECLARE @TaskIds AS TABLE(Id INT)
UPDATE tblTasks
SET Status = 'Started'
OUTPUT INSERTED.Id INTO @TaskIdS
WHERE TaskId = @TaskId
[... - Do something with @TaskIds - not relevant]
假设我需要select + update来实现我的需求 - 我怎样才能确保在现有流程完成之前,其他流程都不会执行第一次操作(select)?
据我所知,甚至Serializable隔离级别的事务在这里还不够,因为其他进程可以读取数据,然后等到我完成(因为它的更新被锁定)并更新我刚刚更新的数据。 / p>
我觉得表提示XLOCK
或HOLDLOCK
可能会有所帮助,但我不是专家,MS医生让我害怕:
注意
由于SQL Server查询优化器通常会为查询选择最佳执行计划,因此我们建议有经验的开发人员和数据库管理员仅将提示用作最后的手段。
(来自https://docs.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table)
那么我如何确保两个进程不会更新一个项目呢?如何确保如果一个进程正在运行另一个进程将等待并在第一次完成后执行其工作而不是失败?
答案 0 :(得分:0)
通常,SQL Server会自动锁定每个步骤,直到您拥有GO
或到达脚本的末尾。
根据我的理解,您想要/需要的是SELECT/UPDATE
"一气呵成的方式"。您应该能够通过TRANSACTION
,TRY ... CATCH
和CTE的组合来实现这一目标。
DECLARE @TaskIds AS TABLE (TaskId INT);
BEGIN TRANSACTION;
BEGIN TRY
WITH myTasks (TaskId) AS (
SELECT TOP 1 t.TaskId
FROM tblTasks AS t
WHERE t.Status = 'Created'
)
UPDATE t
SET t.Status = 'Started'
OUTPUT INSERTED.TaskId INTO @TaskIds
FROM tblTasks AS t
INNER JOIN myTasks AS mt
ON mt.TaskId = t.TaskId;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION;
THROW;
END CATCH;
IF @@TRANCOUNT > 0
BEGIN
COMMIT TRANSACTION;
SELECT TaskId FROM @TaskIds;
[.. do other stuff ..]
END
GO