web-dev-qa-db-ja.com

SQL Serverで、Oracleの「SELECT FOR UPDATE WAIT」に似た方法で単一の行をロックするにはどうすればよいですか?

Oracleデータベースに接続して操作を実行するプログラムがあります。このプログラムを、SQL Serverデータベースもサポートするように調整したいと思います。

Oracleバージョンでは、「SELECT FOR UPDATE WAIT」を使用して、必要な特定の行をロックします。更新がSELECTの結果に基づいており、他のセッションでは絶対に同時に変更できないため、最初に手動でロックする必要がある場合に使用します。システムは、同じデータに同時にアクセスしようとするセッションの影響を強く受けます。

例えば:
2人のユーザーが、データベース内の行を最も高い優先度でフェッチし、ビジーとしてマークし、操作を実行し、後で使用できるように再び使用可能としてマークしようとします。 Oracleでは、ロジックは基本的に次のようになります。

BEGIN TRANSACTION;
SELECT ITEM_ID FROM TABLE_ITEM WHERE ITEM_PRIORITY > 10 AND ITEM_CATEGORY = 'CT1'
    ITEM_STATUS = 'available' AND ROWNUM = 1 FOR UPDATE WAIT 5;
UPDATE [locked item_id] SET ITEM_STATUS = 'unavailable';
COMMIT TRANSACTION;

クエリはコード内で動的に作成されることに注意してください。また、前に最も有利な行が使用不可としてマークされると、2番目のユーザーが次の行に自動的に移動することにも注意してください。さらに、異なるカテゴリで作業する異なるユーザーは、互いのロックが解除されるのを待つ必要はありません。最悪の場合は5秒後にエラーが返され、操作はキャンセルされます。

最後に、質問は次のとおりです。SQLServerで同じ結果を得るにはどうすればよいですか。私は理論的には動作するはずのロックのヒントを見てきました。ただし、他のロックを防止するロックは「UPDLOCK」と「XLOCK」のみであり、両方ともテーブルレベルでのみ機能します。
行レベルで機能するロックヒントはすべて共有ロックであり、これも私のニーズを満たしていません(両方のユーザーが同じ行を同時にロックでき、両方とも使用不可としてマークし、重複操作を実行できます)対応するアイテム)。

一部の人々は「時間変更」列を追加するように見えるので、セッションがそれを変更した人であることをセッションが確認できますが、これは多くの冗長で不必要なアクセスがあるように聞こえます。

21
Paradoxyde

SQL Serverにはロックヒントがありますが、提供したOracleの例のように、それらのステートメントに及ぶことはありません。 SQL Serverでこれを行う方法は、実行するステートメントを含むトランザクションに分離レベルを設定することです。 このMSDNページ を参照してください。ただし、一般的な構造は次のようになります。

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

BEGIN TRANSACTION;

    select * from ...

    update ...

COMMIT TRANSACTION;

SERIALIZABLEは最高の分離レベルです。他のオプションについてはリンクをご覧ください。 MSDNから:

SERIALIZABLE次を指定します。

ステートメントは、変更されたが他のトランザクションによってまだコミットされていないデータを読み取ることはできません。

他のトランザクションは、現在のトランザクションが完了するまで、現在のトランザクションによって読み取られたデータを変更できません。

他のトランザクションは、現在のトランザクションが完了するまで、現在のトランザクション内のステートメントによって読み取られたキーの範囲に入るキー値を持つ新しい行を挿入できません。

9
Paul Sasik

おそらくforwith (updlock, holdlock)を探しています。これにより、selectロックではなく、exclusiveロックが更新に必要なsharedロックを取得します。 holdlockヒントは、トランザクションが終了するまでロックを保持するようSQL Serverに指示します。

FROM TABLE_ITEM with (updlock, holdlock)
16
Andomar

ドキュメント のように:

XLOCK

トランザクションが完了するまで排他ロックを取得して保持することを指定します。 ROWLOCK、PAGLOCK、またはTABLOCKで指定された場合、排他ロックは適切なレベルの粒度に適用されます。

したがって、解決策はWITH(XLOCK, ROWLOCK)を使用しています。

BEGIN TRANSACTION;

SELECT ITEM_ID
FROM TABLE_ITEM
WITH(XLOCK, ROWLOCK)
WHERE ITEM_PRIORITY > 10 AND ITEM_CATEGORY = 'CT1' AND ITEM_STATUS = 'available' AND ROWNUM = 1;

UPDATE [locked item_id] SET ITEM_STATUS = 'unavailable';

COMMIT TRANSACTION;
9
Guido Mocha

WITH(ROWLOCK)を試しましたか?

BEGIN TRAN

   UPDATE your_table WITH (ROWLOCK)
   SET your_field = a_value
   WHERE <a predicate>

COMMIT TRAN
1
Pablushka