データベーステーブルをキューとして使用したい。それに挿入し、挿入された順序(FIFO)で要素を取得します。毎秒これらのトランザクションが何千もあるので、私の主な考慮事項はパフォーマンスです。そこで、テーブル全体を検索せずに最初の要素を提供するSQLクエリを使用します。行を読み取ったときに削除しません。 SELECT TOP 1 .....ここで役立ちますか?特別なインデックスを使用する必要がありますか?
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のようなものを使用します。
処理された行を削除しない場合、行がすでに処理されたことを示す何らかのフラグが必要になります。
そのフラグと、並べ替える列にインデックスを付けます。
そのフラグでテーブルをパーティションに分割して、デキューされたトランザクションがクエリを詰まらせないようにします。
本当に1.000
メッセージが毎秒、86.400.000
1日1行。古い行をクリーンアップする何らかの方法を考えたいかもしれません。
すべては、データベースエンジン/実装に依存します。
私にとって、次の列を持つテーブルの単純なキュー:
id / task / priority / date_added
通常は動作します。
優先度とタスクを使用してタスクをグループ化し、タスクが2倍になった場合は、優先度の高いものを選択しました。
そして心配しないでください-現代のデータベースにとって「数千」は特別なものではありません。
挿入の日時を追跡するために何かを使用する限り、これはまったく問題になりません。 mysql options についてはこちらをご覧ください。問題は、最後に送信された絶対的なアイテムのみが必要か、反復する必要があるかです。反復する必要がある場合は、ORDER BY
ステートメントを使用してチャンクを取得し、ループスルーし、最後の日時を覚えてください次のチャンクを取得するときにそれを使用できるようにします。
たぶん、selectステートメントにLIMIT = 1を追加すると役立つでしょう... 1回の一致の後に強制的に戻ります...
日付(または自動インクリメント)列にクラスター化インデックスを作成します。これにより、テーブル内の行がほぼインデックス順に維持され、ORDER BY
インデックス付けされた列のインデックスベースの高速アクセスが可能になります。 TOP X
(またはRDMBSに応じてLIMIT X
)を使用すると、インデックスから最初のxアイテムのみが取得されます。
パフォーマンスの警告:クエリの実行計画(実際のデータ)を常に確認して、オプティマイザーが予期しないことをしないことを確認する必要があります。また、情報に基づいた意思決定を行えるように、クエリをベンチマークします(実際のデータで再度)。
テーブルからレコードを削除しないため、(processed, id)
の複合インデックスが必要です。processed
は、現在のレコードが処理されたかどうかを示す列です。
最善の方法は、レコード用のパーティションテーブルを作成し、PROCESSED
フィールドをパーティションキーにすることです。これにより、3つ以上のローカルインデックスを保持できます。
ただし、常にid
順序でレコードを処理し、状態が2つしかない場合、レコードを更新することは、インデックスの最初の葉からレコードを取得し、最後の葉に追加することを意味します
現在処理されているレコードは、すべての未処理レコードの中で最も小さいid
と、処理されたすべてのレコードの中で最も大きいid
を常に持っています。
「テーブルをキューにするにはどうすればよいか」という同じ一般的な質問がありましたが、私が望む答えはどこにも見つかりませんでした。
これが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つのクエリですべての変更を取得したくない場合は、最後のバージョンで上位n個の順序を取得し、再度クエリする必要がある最大バージョンを保存できます。
例についてはこちらをご覧ください SQL Server 2008での変更追跡の使用