web-dev-qa-db-ja.com

データベーステーブルをキューとして使用する

データベーステーブルをキューとして使用したい。それに挿入し、挿入された順序(FIFO)で要素を取得します。毎秒これらのトランザクションが何千もあるので、私の主な考慮事項はパフォーマンスです。そこで、テーブル全体を検索せずに最初の要素を提供するSQLクエリを使用します。行を読み取ったときに削除しません。 SELECT TOP 1 .....ここで役立ちますか?特別なインデックスを使用する必要がありますか?

41
Shayan

IDENTITYフィールドを主キーとして使用して、キューに入れられた各アイテムに一意に増分するIDを提供し、クラスター化インデックスをそのアイテムに貼り付けます。これは、アイテムがキューに入れられた順序を表します。

アイテムを処理中にキューテーブルに保持するには、特定のアイテムの現在のステータスを示す「ステータス」フィールドが必要です(例:0 =待機中、1 =処理中、2 =処理済み)。これは、アイテムが2回処理されるのを防ぐために必要です。

キュー内のアイテムを処理する場合、現在処理されていないテーブル内の次のアイテムを見つける必要があります。これは、以下に示すように、複数のプロセスが同じアイテムを拾って同時に処理するのを防ぐような方法である必要があります。 テーブルヒント UPDLOCKおよびREADPASTに注意してください。これらは、キューを実装するときに注意する必要があります。

例えばsproc内では、次のようなものです。

DECLARE @NextID INTEGER

BEGIN TRANSACTION

-- Find the next queued item that is waiting to be processed
SELECT TOP 1 @NextID = ID
FROM MyQueueTable WITH (UPDLOCK, READPAST)
WHERE StateField = 0
ORDER BY ID ASC

-- if we've found one, mark it as being processed
IF @NextId IS NOT NULL
    UPDATE MyQueueTable SET Status = 1 WHERE ID = @NextId

COMMIT TRANSACTION

-- If we've got an item from the queue, return to whatever is going to process it
IF @NextId IS NOT NULL
    SELECT * FROM MyQueueTable WHERE ID = @NextID

アイテムの処理に失敗した場合、後でもう一度試すことができますか?その場合は、ステータスを0またはその他にリセットする必要があります。それにはもっと考える必要があります。

または、データベーステーブルをキューとして使用しないで、MSMQのようなものを使用します。

31
AdaTheDev

処理された行を削除しない場合、行がすでに処理されたことを示す何らかのフラグが必要になります。

そのフラグと、並べ替える列にインデックスを付けます。

そのフラグでテーブルをパーティションに分割して、デキューされたトランザクションがクエリを詰まらせないようにします。

本当に1.000メッセージが毎秒、86.400.000 1日1行。古い行をクリーンアップする何らかの方法を考えたいかもしれません。

7
Peter Lang

すべては、データベースエンジン/実装に依存します。

私にとって、次の列を持つテーブルの単純なキュー:

id / task / priority / date_added

通常は動作します。

優先度とタスクを使用してタスクをグループ化し、タスクが2倍になった場合は、優先度の高いものを選択しました。

そして心配しないでください-現代のデータベースにとって「数千」は特別なものではありません。

4
bluszcz

挿入の日時を追跡するために何かを使用する限り、これはまったく問題になりません。 mysql options についてはこちらをご覧ください。問題は、最後に送信された絶対的なアイテムのみが必要か、反復する必要があるかです。反復する必要がある場合は、ORDER BYステートメントを使用してチャンクを取得し、ループスルーし、最後の日時を覚えてください次のチャンクを取得するときにそれを使用できるようにします。

3
David Berger

たぶん、selectステートメントにLIMIT = 1を追加すると役立つでしょう... 1回の一致の後に強制的に戻ります...

2
Reed Debaets

日付(または自動インクリメント)列にクラスター化インデックスを作成します。これにより、テーブル内の行がほぼインデックス順に維持され、ORDER BYインデックス付けされた列のインデックスベースの高速アクセスが可能になります。 TOP X(またはRDMBSに応じてLIMIT X)を使用すると、インデックスから最初のxアイテムのみが取得されます。

パフォーマンスの警告:クエリの実行計画(実際のデータ)を常に確認して、オプティマイザーが予期しないことをしないことを確認する必要があります。また、情報に基づいた意思決定を行えるように、クエリをベンチマークします(実際のデータで再度)。

2
David Schmitt

テーブルからレコードを削除しないため、(processed, id)の複合インデックスが必要です。processedは、現在のレコードが処理されたかどうかを示す列です。

最善の方法は、レコード用のパーティションテーブルを作成し、PROCESSEDフィールドをパーティションキーにすることです。これにより、3つ以上のローカルインデックスを保持できます。

ただし、常にid順序でレコードを処理し、状態が2つしかない場合、レコードを更新することは、インデックスの最初の葉からレコードを取得し、最後の葉に追加することを意味します

現在処理されているレコードは、すべての未処理レコードの中で最も小さいidと、処理されたすべてのレコードの中で最も大きいidを常に持っています。

2
Quassnoi

「テーブルをキューにするにはどうすればよいか」という同じ一般的な質問がありましたが、私が望む答えはどこにも見つかりませんでした。

これがNode/SQLite/better-sqlite3で思いついたものです。基本的には、ユースケースに合わせて内側のWHEREおよびORDER BY句を変更するだけです。

module.exports.pickBatchInstructions = (db, batchSize) => {
  const buf = crypto.randomBytes(8); // Create a unique batch identifier

  const q_pickBatch = `
    UPDATE
      instructions
    SET
      status = '${status.INSTRUCTION_INPROGRESS}',  
      run_id = '${buf.toString("hex")}',
      mdate = datetime(datetime(), 'localtime')
    WHERE
      id IN (SELECT id 
        FROM instructions 
        WHERE 
          status is not '${status.INSTRUCTION_COMPLETE}'
          and run_id is null
        ORDER BY
          length(targetpath), id
        LIMIT ${batchSize});
  `;
  db.run(q_pickBatch); // Change the status and set the run id

  const q_getInstructions = `
    SELECT
      *
    FROM
      instructions
    WHERE
      run_id = '${buf.toString("hex")}'
  `;
  const rows = db.all(q_getInstructions); // Get all rows with this batch id

  return rows;
};
1
Daniel Kaplan

トランザクションやロックなどを持たないための非常に簡単なソリューションは、変更追跡メカニズムを使用することです(データキャプチャではありません)。追加/更新/削除された各行のバージョン管理を利用するため、特定のバージョンの後にどのような変更が発生したかを追跡できます。

したがって、最後のバージョンを保持し、新しい変更を照会します。

クエリが失敗した場合は、いつでも戻って最後のバージョンのデータをクエリできます。また、1つのクエリですべての変更を取得したくない場合は、最後のバージョンで上位n個の順序を取得し、再度クエリする必要がある最大バージョンを保存できます。

例についてはこちらをご覧ください SQL Server 2008での変更追跡の使用

0