我经常编写更新数百万行数据的datascrub。数据驻留在使用InnoDB的24x7x365 OLTP MySQL数据库中。更新可以擦除表的每一行(其中DB最终获取表级锁)或者可能只是擦除表中的10%的行(可能仍然是数百万)。
为了避免创建大量的事务大小并最小化争用,我通常最终试图将我的一个大量UPDATE语句分解为一系列较小的UPDATE事务。所以我最终编写了一个循环结构,它限制了我的UPDATE的WHERE子句,如下所示:
(警告:这只是用于获取要点的伪代码)
@batch_size=10000;
@max_primary_key_value = select max(pk) from table1
for (int i=0; i<=@max_primary_key_value; i=i+@batch_size)
{
start transaction;
update IGNORE table1
set col2 = "flag set"
where col2 = "flag not set"
and pk > i
and pk < i+@batchsize;
commit;
}
由于种种原因,这种方法很简单。
我想发出一个UPDATE语句,而数据库不会尝试将所有正在更新的记录分组到一个事务单元中。我不希望UPDATE作为一个单独的工作单元成功或失败。如果1/2行无法更新......没问题,请告诉我。从本质上讲,每一行都是它自己的工作单元,但批处理或光标是我能弄清楚如何将其表示给数据库引擎的唯一方法。
我查看了为会话设置隔离级别,但在这种特定情况下,这似乎对我没有帮助。
还有其他想法吗?
答案 0 :(得分:2)
也许不是您正在寻找的答案,但您可以通过在更新中使用LIMIT来简化您的代码。
的伪代码:
do {
update table1 set col2 = 'flag set' where col2 = 'flat not set' LIMIT 10000
} while (ROW_COUNT() > 0)
答案 1 :(得分:0)
是的Eric ...你是对的(对于那些不包括“不在哪里”这样的子句的简单案例)。我编写了一个小程序,允许我提供SQL更新语句和限制数作为参数。
create procedure mass_update(IN updatestmt TEXT,IN batchsiz INT) 开始 - 目的:将大型更新语句分解为批处理以限制事务大小并减少争用 - 限制:仅适用于在执行TWICE时会产生“0行受影响”的UPDATE!
SET @sql = CONCAT( updatestmt," LIMIT ", batchsiz );
-- had to use CONCAT because "PREPARE stmt FROM" cannot accept dynamic LIMIT parameter
-- reference: http://forums.mysql.com/read.php?98,75640,75640#msg-75640
PREPARE stmt FROM @sql;
select @sql; --display SQL to screen
SET @cumrowcount=0;
SET @batchnum=0;
SET @now := now(); -- @now is a STRING variable... not a datetime
increment: repeat
SET @batchnum=@batchnum+1;
EXECUTE stmt;
set @rowcount = ROW_COUNT();
set @cumrowcount = @cumrowcount + @rowcount;
select @batchnum as "Iteration",
@cumrowcount as "Cumulative Rows",
TIMESTAMPDIFF(SECOND,STR_TO_DATE(@now,"%Y-%m-%d %H:%i:%s"),now()) as "Cumulative Seconds",
now() as "Timestamp";
until @rowcount <= 0
end repeat increment;
DEALLOCATE PREPARE stmt; -- REQUIRED
END
这似乎运行得相当好,我可以使用任何旧的UPDATE语句来运行它,该语句遵循“在0行中运行两次结果”规则。
感谢Eric的想法!