使用SELECT ... FOR UPDATE,然后更新多行

时间:2015-07-13 20:30:06

标签: mysql

我正在写一个应用程序:

  • 从订阅者表中选择一个小记录集(150k记录);
  • 更新这些行以指示正在发送电子邮件;
  • 向记录集中的订阅者发送电子邮件;
  • 再次更新行以指示电子邮件已发送。

皱纹是多个客户端同时访问该表以分发电子邮件工作负载,这就是为什么使用中间更新(以指示进程内) - 以防止不同的客户端选择相同的行,这会导致多个电子邮件发送到同一订户。我已经应用了一些随机逻辑来降低两个客户端使用相同数据的可能性,但偶尔也会发生这种情况。

所以现在我正在使用SELECT ... FOR UPDATE来锁定相关的行(因此另一个客户端不会选择它们)。我的问题:根据SELECT...FOR UPDATE语句的ID编写UPDATE语句或创建循环来单独更新每一行是否更好?

这是我到目前为止所得到的:

DELIMITER $$

CREATE DEFINER=`mydef`@`%` PROCEDURE `sp_SubscribersToSend`(v_limit INTEGER)
BEGIN

START TRANSACTION;

SELECT _ID, email, date_entered, DATE_FORMAT(date_entered, '%b %e, %Y') AS 'date_entered_formatted'
FROM _subscribers
WHERE send_state = 'Send'
AND status = 'Confirmed'
LIMIT v_limit
FOR UPDATE;

[[UPDATE _subscribers SET send_state = 'Sending' WHERE _ID IN (...?)]]

[[OR]]

[[Loop through the resultset and update each row?]]
COMMIT;

END

似乎单个UPDATE会更有效率;将结果集的_ID列转换为IN()子句的逗号分隔列表的最佳方法是什么? (在此之前我一直在做这个客户端) - 或者是否有一个更好的方法?

2 个答案:

答案 0 :(得分:0)

不要尝试创建以逗号分隔的列表,只需使用与UPDATE

相同的条件执行SELECT即可
START TRANSACTION;

UPDATE _subscribers
SET send_state = 'Sending'
WHERE send_state = 'Send'
AND status = 'Confirmed'
ORDER BY <something>
LIMIT v_limit;

SELECT _ID, email, date_entered, DATE_FORMAT(date_entered, '%b %e, %Y') AS 'date_entered_formatted'
FROM _subscribers
WHERE send_state = 'Send'
AND status = 'Confirmed'
ORDER BY <something>
LIMIT v_limit;

COMMIT;

ORDER BY子句是确保两个查询处理相同行所必需的;如果您使用LIMIT而不使用ORDER BY,则可以选择不同的行子集。

答案 1 :(得分:0)

感谢Barmar,我在存储过程中采用了不同的方法:

SET @IDs := null;

UPDATE _subscribers
SET send_state = 'Sending'
WHERE send_state = 'Send'
AND status = 'Confirmed'
AND (SELECT @IDs := CONCAT_WS(',', _ID, @IDs) )
LIMIT v_limit;

SELECT CONVERT(@IDs USING utf8);

正如Barmar建议的那样,它会执行UPDATE,但也会将要更新的行的ID连接到变量中。只需SELECT该变量,它就会为您提供逗号分隔的列表,可以将其传递到PREPARE语句中。 (我必须使用CONVERT,因为SELECT变量返回二进制/ blob值)。所以...这不像我原先打算那样使用SELECT...FOR UPDATE,但它确实确保不同的客户不会使用相同的行。