2017-03-26 8 views
0

次のトランザクションロックを達成する方法は?ロックトランザクションまたはテーブルヒントを使用したT-SQLの選択と更新

大きな単純化 - ステータスが「作成済み」、開始済み、完了済みの「タスク」の表があります。私はストアドプロシージャGetNextを作成して、まだ開始されていないトップ1のタスクを取得したい(Createdステータス)。

この手順では、タスクをStartedとマークします。明らかに、2つのプロセスがこのプロシージャを呼び出して同じタスクを取得する状況を回避したいと思います。

このプロシージャは頻繁に呼び出されないため、パフォーマンスは問題ではないため、データを破損しない状態に保つことが重要です。

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:ようなものが必要私も、私はちょうど私の上にあるものよりも、そうではなく、更新タスクを受信したい

UPDATE tblTasks 
SET Status = 'Started' 
WHERE TaskId = (SELECT TOP 1 TaskId 
       FROM tblTasks 
       WHERE Status = 'Created') 

は、だから私はこのような何かをしたいです

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] 

私が必要とするものを達成するには、選択+更新が必要であると仮定します。他のプロセスは、既存のプロセスが完了するまで最初の操作(選択)さえ実行しませんか?

他のプロセスがデータを読み取ることができるので、トランザクションのシリアライズ可能な分離レベルは十分ではありません。(更新がロックによって保持されているため)終了し、更新したばかりのデータを更新します。

私はそのテーブルがXLOCKHOLDLOCKが役立つかもしれないヒントを感じるが、私は何の専門家だとMSのドキュメントは、私を怖がっ:

注意
SQL Serverクエリオプティマイザは、通常のための最適な実行計画を選択しているので経験豊富な開発者とデータベース管理者がヒントを最後の手段として使用することを推奨します。

https://docs.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-tableから)

それでは、どのように私は2つのプロセスが一つの項目を更新しないことを確認してくださいともどのように私は1つのプロセスが他を実行している場合、後にその仕事を待って行いますことを確認してください最初に失敗するのではなく終わりますか?

+0

サイドノート - 「order by」句を指定せずに「トップ1」を使用することは、「最初のレコードを取得する」という意味ではなく、単に「1レコードを取得する」ことを意味します。これは、データベース表が性質上順序付けされていないため、 '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の「一回で」です。 TRANSACTIONTRY ... 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 
関連する問題