mysql存储过程从基于数据库的队列中弹出

时间:2013-01-01 01:43:43

标签: mysql sql database stored-procedures mybatis

我们有一个系统,它有一个基于数据库的队列,用于处理线程中的项目而不是实时。它目前在Mybatis中实现,在mysql中调用这个存储过程:

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调用此存储过程,它将从队列中弹出五个项目并开始处理它们,但只有第五个项目将设置lastPopDate,因此当下一个线程出现并弹出队列时它将获取项目1-4和第6项。

我们如何才能更新所有N条记录&#39;弹出&#39;关闭数据库?

2 个答案:

答案 0 :(得分: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()函数返回唯一值,每台机器的调用次数不应超过1600万次。请访问here了解详情。

为了提高性能,您可能希望将lastPopDate字段更改为NOT NULL,因为OR子句会导致您的查询不使用索引,即使有一个索引可用:< / p>

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

然后,如果您还没有,可以在companyid / lastPopDate / uuid字段中添加索引,如下所示:

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;

将使用您刚创建的索引。

答案 1 :(得分: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可能会导致死锁(当然,如果从不同的会话调用过程) - 据我所知,哪些行被锁定的顺序无法保证(即使你有{{ 1}},行可能以不同的顺序锁定)。我建议仔细检查文档。