2013-01-01 46 views
3

我们有一个系统具有基于数据库的队列,用于处理线程中的项目而不是实时。它目前的MyBatis实现调用MySQL中的这个存储过程: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条记录'弹出'数据库?

回答

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 NULLOR条款将导致您的查询不会使用索引,即使一个是可供选择:

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); 

然后你就可以删除从UPDATE查询子句:

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

将使用刚刚创建的索引。

+0

这是一个非常完整的答案感谢您花时间。我将要实现与此非常接近的事情并对其进行测试。我有点担心的一件事是,更新和select之间似乎存在小的竞争条件,因为记录没有被锁定为“FOR UPDATE”。这几乎肯定是一个比我们现在拥有的小得多的问题。这大部分都是我们使用的 – kasdega

+0

。我们在UUID_SHORT()方面遇到了一些小问题(在mysql中不支持),所以我们使用了UUID()和varchar,而且我们遇到了排序问题,并通过在guid列上显式设置排序规则来比较我们解决的问题。除此之外,它已经实施并正在工作。谢谢。 – kasdega

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,行可能被锁定在不同的顺序)。我建议仔细检查文档。