2017-03-26 21 views
0

如何實現以下事務鎖定?T-SQL選擇並更新鎖定 - 事務或表提示

大簡化 - 我有一個包含狀態(創建,啓動,完成)的「任務」表。我想創建存儲過程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] 

因此,假設我需要選擇+更新來實現我所需要的 - 我如何確保t即使第一次操作(選擇),其他進程也不會執行直到現有進程完成?

據我瞭解,即使可串行化隔離級別的事務在這裏是不夠的,因爲其他進程可以讀取數據,然後等到我完成(因爲它的更新被鎖持有)並更新剛剛更新的數據。

我覺得表提示XLOCKHOLDLOCK可能有幫助,但我不是專家和MS文檔嚇我:

注意
因爲SQL Server查詢優化器通常選擇一個最佳的執行計劃查詢,我們建議提示僅作爲經驗豐富的開發人員和數據庫管理員的最後手段使用。

(從https://docs.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table

那麼,如何確保兩個進程將無法更新一個項目,也是我該如何確保,如果一個進程正在運行的其它將等待後完成其工作第一次結束而不是失敗?

+0

備註 - 使用'top 1'而不指定'order by'子句並不意味着「獲取第一條記錄」,它僅僅意味着「獲取一條記錄」。這是因爲數據庫表本質上是無序的,所以如果沒有'order by'子句,關係數據庫就不能保證返回記錄的順序。 –

+0

您應該閱讀[這篇Dan Guzman的文章](http:// weblogs。sqlteam.com/dang/archive/2007/10/28/Conditional-INSERTUPDATE-Race-Condition.aspx) –

回答

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