必须用 repeat...until row_count()=0 循环,因 while(select count()>0) 会全表扫描且 mvcc 下 count() 可能基于旧快照致漏删或死循环;需 insert...select...limit n 与 delete...limit n 成对、条件一致,并先 insert 后 delete,整个循环须在显式事务中并声明异常处理器。

必须用 REPEAT ... UNTIL ROW_COUNT() = 0 循环,每次只处理固定行数(如 LIMIT 1000),否则极易锁表、超时或触发 innodb_lock_wait_timeout。
为什么不能用 WHILE (SELECT COUNT(*) > 0) 控制循环
这种写法会强制每次循环都全表扫描一次 COUNT(*),在大表上开销极大;更严重的是,COUNT(*) 在 MVCC 下可能基于旧快照,导致漏删或死循环。实际归档中你根本不需要知道“总共有多少行”,只需要确认“这次有没有删掉数据”。
-
ROW_COUNT()是上一条INSERT或DELETE影响的行数,精准、轻量、事务安全 - 循环体里必须是
INSERT ... SELECT ... LIMIT N+DELETE ... LIMIT N成对出现 - 两个语句的
WHERE条件必须完全一致,否则会出现归档了但没删、或删了但没归档
REPEAT 循环里 INSERT 和 DELETE 的顺序与事务边界
先 INSERT 后 DELETE 是安全底线:哪怕 DELETE 失败,数据至少已落库到归档表,不会丢失。整个循环块必须包裹在显式事务中,且需捕获异常。
- 开头加
START TRANSACTION,结尾根据成功与否COMMIT或ROLLBACK - 必须声明
DECLARE EXIT HANDLER FOR SQLEXCEPTION,否则出错就中断,留下部分归档、部分残留的脏状态 - 不要把
COMMIT放在循环内部——每批都提交会放大 binlog 体积、拖慢主从同步;但也不要等到最后才提交——单事务太大会撑爆 undo log
按天归档时 WHERE 条件字段必须有索引
比如归档 create_time ,那 <code>create_time 字段没索引的话,LIMIT 1000 就失去意义:MySQL 仍要扫描全表才能找出前 1000 行,锁和耗时不减反增。
- 推荐建联合索引,例如
(create_time, id),避免回表 - 如果表有分区(如按月
RANGE分区),归档旧分区可直接用ALTER TABLE ... DROP PARTITION,比逐行操作快几个数量级 - 注意时区问题:
NOW()返回服务器时区时间,而业务数据可能是 UTC 存储,条件里要用CONVERT_TZ()对齐
真正难的不是写循环语法,而是判断哪天的数据该归、归到哪、归档后怎么验证一致性——这些逻辑一旦嵌进存储过程,就很难动态调整。建议把归档日期作为输入参数传入,而不是硬编码在过程体里。











