処理のためにレコードを「チェックアウト」するための戦略


10

これに名前の付いたパターンがあるのか​​、それともひどい考えなのでないのかわかりません。しかし、アクティブ/アクティブの負荷分散された環境で動作するサービスが必要です。これはアプリケーションサーバーのみです。データベースは別のサーバーに配置されます。テーブル内の各レコードのプロセスを実行する必要があるサービスがあります。このプロセスには1〜2分かかる場合があり、n分ごとに繰り返されます(構成可能、通常は15分)。

この処理を必要とする1000レコードのテーブルと、この同じデータセットに対して実行される2つのサービスで、各サービスに処理するレコードを「チェックアウト」させたいと思います。一度に1つのサービス/スレッドのみが各レコードを処理していることを確認する必要があります。

過去に「ロックテーブル」を使用していた同僚がいます。他のテーブルのレコードを論理的にロックするためにレコードがこのテーブルに書き込まれ(他のテーブルはかなり静的で、非常に時々新しいレコードが追加されます)、削除されてロックが解放されます。

新しいテーブルが常に削除を挿入するのではなく、いつロックされ、現在ロックされているかを示す列があるとよいのではないかと思います。

誰かがこの種のことについてのヒントを持っていますか?長期的な論理ロックの確立されたパターンはありますか?一度に1つのサービスのみがロックを取得するようにするためのヒントはありますか?(私の同僚はTABLOCKXを使用してテーブル全体をロックしています。)

回答:


12

私は、余分な「ロック」テーブルや、次のレコードを取得するためにテーブル全体をロックするというアイデアの大ファンではありません。私はそれが行われている理由を理解していますが、ロックされたレコードを解放するために更新している操作の同時実行性にも害を与えています(2つのプロセスが同じレコードを同時)。

私の好みは、ProcessStatusID(通常はTINYINT)列を、処理中のデータを含むテーブルに追加することです。また、LastModifiedDateのフィールドはありますか?そうでない場合は、追加する必要があります。はいの場合、これらのレコードはこの処理の外で更新されますか?この特定のプロセス外でレコードを更新できる場合は、別のフィールドを追加して、StatusModifiedDate(またはそのようなもの)を追跡する必要があります。この回答の残りの部分では、「StatusModifiedDate」を使用します。これは、その意味が明らかであるためです(実際、「LastModifiedDate」フィールドがない場合でも、実際にはフィールド名として使用できます)。

ProcessStatusIDの値(「ProcessStatus」という新しいルックアップテーブルに配置し、このテーブルに外部キーを設定する必要があります)は次のようになります。

  1. 完了(この場合は「保留中」でもあります。どちらも「処理する準備ができている」ことを意味するため)
  2. 処理中(または「処理中」)
  3. エラー(または「WTF?」)

この時点で、アプリケーションから次のレコードを取得して処理したいだけで、その決定を下すために何も渡さないと想定するのは安全だと思われます。そのため、「完了」/「保留」に設定されている(少なくともStatusModifiedDateに関して)最も古いレコードを取得したいとします。以下に沿ったもの:

SELECT TOP 1 pt.RecordID
FROM   ProcessTable pt
WHERE  pt.StatusID = 1
ORDER BY pt.StatusModifiedDate ASC;

また、他のプロセスがレコードを取得しないように、同時にそのレコードを「処理中」に更新したいと考えています。このOUTPUT句を使用して、同じトランザクションでUPDATEとSELECTを実行できます。

UPDATE TOP (1) pt
SET    pt.StatusID = 2,
       pt.StatusModifiedDate = GETDATE() -- or GETUTCDATE()
OUTPUT INSERTED.RecordID
FROM   ProcessTable pt
WHERE  pt.StatusID = 1;

ここでの主な問題はTOP (1)UPDATE操作でaを実行できる一方で、を実行する方法がないことORDER BYです。しかし、それをCTEでラップして、これら2つの概念を組み合わせることができます。

;WITH cte AS
(
   SELECT TOP 1 pt.RecordID
   FROM   ProcessTable pt (READPAST, ROWLOCK, UPDLOCK)
   WHERE  pt.StatusID = 1
   ORDER BY pt.StatusModifiedDate ASC;
)
UPDATE cte
SET    cte.StatusID = 2,
       cte.StatusModifiedDate = GETDATE() -- or GETUTCDATE()
OUTPUT INSERTED.RecordID;

明らかな問題は、SELECTを同時に実行する2つのプロセスが同じレコードを取得できるかどうかです。特にREADPASTおよびUPDLOCKヒントと組み合わせて(詳細については以下を参照)、OUTPUT句を使用したUPDATEで問題がないことを確信しています。ただし、私はこの正確なシナリオをテストしていません。なんらかの理由で上記のクエリが競合状態を処理しない場合は、アプリケーションロックを追加します。

上記のCTEクエリは、sp_getapplockおよびsp_releaseapplockでラップして、プロセスの「ゲ​​ートキーパー」を作成できます。そうすることで、上記のクエリを実行するために一度に1つのプロセスのみが入力できます。他のプロセスは、applockを持つプロセスが解放するまでブロックされます。また、プロセス全体のこのステップはRecordIDを取得するだけなので、非常に高速で、他のプロセスを非常に長くブロックすることはありません。また、CTEクエリと同様に、テーブル全体をブロックしないため、他の行への他の更新を許可します(そのステータスを「完了」または「エラー」に設定します)。基本的に:

BEGIN TRANSACTION;
EXEC sp_getapplock @Resource = 'GetNextRecordToProcess', @LockMode = 'Exclusive';

   {CTE UPDATE query shown above}

EXEC sp_releaseapplock @Resource = 'GetNextRecordToProcess';
COMMIT TRANSACTION;

アプリケーションロックは非常に便利ですが、慎重に使用する必要があります。

最後に、ステータスを「完了」または「エラー」に設定するためのストアドプロシージャが必要です。そしてそれは簡単なことです:

CREATE PROCEDURE ProcessTable_SetProcessStatusID
(
   @RecordID INT,
   @ProcessStatusID TINYINT
)
AS
SET NOCOUNT ON;

UPDATE pt
SET    pt.ProcessStatusID = @ProcessStatusID,
       pt.StatusModifiedDate = GETDATE() -- or GETUTCDATE()
FROM   ProcessTable pt
WHERE  pt.RecordID = @RecordID;

テーブルヒント(ヒント(Transact-SQL)-テーブルにあります):

  • READPAST(この正確なシナリオに適合するようです)

    データベースエンジンが他のトランザクションによってロックされている行を読み取らないことを指定します。READPASTが指定されている場合、行レベルのロックはスキップされます。つまり、データベースエンジンは、ロックが解放されるまで現在のトランザクションをブロックするのではなく、行をスキップします。READPASTは、主に、SQL Serverテーブルを使用するワークキューを実装するときにロックの競合を減らすために使用されます。READPASTを使用するキューリーダーは、他のトランザクションによってロックされた過去のキューエントリを次の利用可能なキューエントリにスキップします。他のトランザクションがロックを解放するまで待機する必要はありません。

  • ROWLOCK(安全のために)

    ページまたはテーブルのロックが通常行われるときに行ロックが行われることを指定します。

  • アップロック

    更新ロックが取得され、トランザクションが完了するまで保持されることを指定します。UPDLOCKは、行レベルまたはページレベルでのみ読み取り操作の更新ロックを取得します。


1

Service Brokerキューを使用して(アプリケーションなしで、純粋にDB内で)同様のことを行いました。軽量で、完全にACIDに準拠しており、ほぼ無限にスケールアウトできます。透過的な行ロック(または「非表示」)が組み込まれています。バージョン2005以降で使用できます。

あなたのケースでは、全体的なアーキテクチャは次のようになります。いくつかのプロセスは、スケジュールに従ってService Brokerダイアログにメッセージを送信し、リスナーはターゲット側のキューからメッセージをピックアップします。個別のメッセージタイプを作成することとは別に、メッセージボディにはほとんどすべてのものを含めることができます。

把握するのが最も簡単なことではありませんが、それは確かですが、一度取得すると、その利点が明らかになります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.