3

リアルタイムではなくスレッド内のアイテムを処理するためのデータベースベースのキューを持つシステムがあります。これは現在、mysqlでこのストアドプロシージャを呼び出すMybatisに実装されています。

DROP PROCEDURE IF EXISTS pop_invoice_queue;
DELIMITER ;;
CREATE PROCEDURE pop_invoice_queue(IN compId int(11), IN limitRet int(11)) BEGIN

   SELECT LAST_INSERT_ID(id) as value, InvoiceQueue.* FROM InvoiceQueue 
      WHERE companyid = compId 
      AND (lastPopDate is null OR lastPopDate < DATE_SUB(NOW(), INTERVAL 3 MINUTE)) LIMIT limitRet FOR UPDATE;
   UPDATE InvoiceQueue SET lastPopDate=NOW() WHERE id=LAST_INSERT_ID(); 

END;;

DELIMITER ;

問題は、これがキューからN個のアイテムをポップしますが、キューからポップされた最後のアイテムのlastPopDate値のみを更新することです。したがって、limitRet = 5でこのストアドプロシージャを呼び出すと、キューから5つのアイテムがポップされて作業が開始されますが、lastPopDateが設定されるのは5番目のアイテムのみであるため、次のスレッドが来てキューからポップすると、アイテムが取得されます。 1-4および項目6。

これを取得して、データベースから「ポップ」されたすべてのNレコードを更新するにはどうすればよいですか?

4

2 に答える 2

2

BIGINT次の方法でテーブルにフィールドを追加する場合:

ALTER TABLE InvoiceQueue
ADD uuid BIGINT NULL DEFAULT NULL,
INDEX ix_uuid (uuid);

次に、最初に更新を実行し、次の方法で更新されたレコードを選択できます。

CREATE PROCEDURE pop_invoice_queue(IN compId int(11), IN limitRet int(11))
BEGIN
   SET @uuid = UUID_SHORT();

   UPDATE InvoiceQueue
   SET    uuid = @uuid,
          lastPopDate = NOW()
   WHERE  companyid = compId
   AND    uuid IS NULL 
   AND    (lastPopDate IS NULL OR lastPopDate < NOW() - INTERVAL 3 MINUTE)
   ORDER BY
          id
   LIMIT  limitRet;

   SELECT * 
   FROM   InvoiceQueue 
   WHERE  uuid = @uuid
   FOR    UPDATE;
END;;

関数が一意の値を返すためには、UUID_SHORT()マシンごとに1秒間に1600万回を超えて呼び出さないようにする必要があります。詳細については、をご覧hereください。

パフォーマンスのために、lastPopDateフィールドを変更することをお勧めします。これNOT NULLは、OR句によってインデックスが使用可能であっても、クエリでインデックスが使用されないためです。

ALTER TABLE InvoiceQueue
MODIFY lastPopDate DATETIME NOT NULL DEFAULT '0000-00-00';

次に、まだ持っていない場合は、次のように//フィールドにインデックスを追加companyidできますlastPopDateuuid

ALTER TABLE InvoiceQueue
ADD INDEX ix_company_lastpop (companyid, lastPopDate, uuid);

次に、クエリORから句を削除できます。UPDATE

   UPDATE InvoiceQueue
   SET    uuid = @uuid,
          lastPopDate = NOW()
   WHERE  companyid = compId 
   AND    lastPopDate < NOW() - INTERVAL 3 MINUTE
   ORDER BY
          id
   LIMIT  limitRet;

作成したインデックスを使用します。

于 2013-01-01T03:29:20.477 に答える
0

mysqlにはコレクション句も出力/戻り句もないので、私の提案は一時テーブルを使用することです。何かのようなもの :

CREATE TEMPORARY TABLE temp_data 
SELECT LAST_INSERT_ID(id) as value, InvoiceQueue.* FROM InvoiceQueue 
  WHERE companyid = compId 
  AND (lastPopDate is null OR lastPopDate < DATE_SUB(NOW(), INTERVAL 3 MINUTE)) LIMIT limitRet FOR UPDATE;  
UPDATE InvoiceQueue 
INNER JOIN temp_data ON (InvoiceQueue.PKColumn = temp_data.PKColumn)
SET lastPopDate=NOW();
SELECT * FROM temp_data ;
DROP TEMPORARY TABLE temp_data;

また、これselect ... for updateによりデッドロックが発生する可能性があると推測します(確かに、プロシージャが異なるセッションから呼び出された場合)-行がロックされる順序は保証されていません(たとえあったとしてもorder by、行は異なる順序でロックされる可能性があります)。ドキュメントを再確認することをお勧めします。

于 2013-01-01T02:53:24.010 に答える