我们有一个系统,它有一个基于数据库的队列,用于处理线程中的项目而不是实时。它目前在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;关闭数据库?
答案 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}},行可能以不同的顺序锁定)。我建议仔细检查文档。