归档存储过程不能直接用单条DELETE,因为会锁表或大量行、产生巨量事务日志,易触发超时或OOM;需分段执行,WHERE条件必须走索引且避免函数包裹,确保高效定位旧数据。

归档存储过程为什么不能直接用单条 DELETE?
因为大表归档时,单次 DELETE 会锁表(或锁大量行)、产生巨量事务日志、可能触发超时或 OOM。MySQL 的 innodb_log_file_size 和 SQL Server 的事务日志截断机制都扛不住一次性删几百万行。真实业务里,你看到的“卡住”“锁等待超时”“日志空间不足”,八成是没分段。
WHERE 条件必须走索引,且避免函数包裹
归档的核心是高效定位旧数据,WHERE create_time 是安全的;但写成 <code>WHERE DATE(create_time) 就会让索引失效——全表扫描一开,分段也白搭。
- 先用
EXPLAIN确认执行计划是否走了索引(type字段至少是range,不是ALL) - 时间字段建议用
datetime或bigint(时间戳),别用varchar存日期字符串 - 复合归档条件(如
status = 'archived' AND create_time )要确保联合索引顺序匹配
分段 DELETE 的安全循环写法(以 MySQL 为例)
靠 LIMIT 控制每次删多少,但必须配合主键/有序字段推进,否则可能漏删或重复删。不要用 OFFSET,它在大偏移时性能极差。
WHILE ROW_COUNT() > 0 DO
DELETE FROM orders
WHERE create_time < '2023-01-01'
AND id <= (SELECT id FROM orders WHERE create_time < '2023-01-01' ORDER BY id DESC LIMIT 1 OFFSET 999)
ORDER BY id DESC
LIMIT 1000;
-- 加个短暂停,减轻主从延迟和锁竞争(可选)
DO SLEEP(0.1);
END WHILE;- 关键点:子查询用
ORDER BY id DESC LIMIT 1 OFFSET 999找到本次删除的“上界”,确保每次删的是连续一批 -
ORDER BY id DESC LIMIT 1000保证按主键倒序删,避免间隙锁扩散 - 循环外建议加
SELECT COUNT(*)预估总行数,超过阈值(如 50 万)就强制走分段,不给单次机会
归档前务必先 INSERT ... SELECT 到历史表,再删原表
归档不是只删,而是“搬+删”。如果先删后插,失败就丢数据;如果边删边插又难回滚。稳妥做法是两步原子化:先批量插入归档表(带 IGNORE 或 ON DUPLICATE KEY UPDATE 防重),确认成功后再删源数据。
- 归档表结构要和源表一致,但可去掉非必要索引——归档表通常只查不更新
- 插入时用
INSERT INTO archive_orders SELECT * FROM orders WHERE ... LIMIT 1000,同样分批 - 生产环境建议在低峰期跑,且监控
innodb_row_lock_waits和从库延迟
分段逻辑本身不难,难的是判断“哪一段该删”“删完怎么验”“出错怎么续”。很多人卡在子查询边界计算和事务隔离级别引发的幻读上,这点在高并发写入场景下尤其容易被忽略。


















