冷热数据分离不能只靠定时sql直接delete或insert...select,因其易引发锁表、主从延迟飙升、binlog暴增;需用存储过程封装事务、分批控制与状态校验,通过get_lock防重入、row_count()控批量、replace into保原子性。

冷热数据分离为什么不能只靠定时SQL
直接用 DELETE 或 INSERT ... SELECT 配合事件调度器(EVENT)看似简单,但实际运行中常遇到锁表、主从延迟飙升、binlog暴增等问题。存储过程能封装事务边界、控制批量粒度、加入状态检查,是更可控的选择。
关键不是“能不能做”,而是“怎么避免把线上库跑挂”。MySQL 8.0 的 ROW_COUNT()、GET_LOCK() 和原子性 REPLACE INTO 是稳住节奏的核心工具。
存储过程必须包含的三个安全层
一个能进生产环境的冷热分离存储过程,至少要过这三关:
-
防重入:用
GET_LOCK('hot_to_cold_lock', 0)尝试获取全局锁,返回 0 直接LEAVE,避免多个实例并发执行导致数据错乱 - 分批控制
-
状态校验:执行前查
SELECT COUNT(*) FROM hot_table WHERE create_time ,若为 0 则退出,不浪费资源
示例:带事务与日志记录的迁移逻辑
以下过程将 orders 表中 90 天前的数据迁至 orders_archive,并记录操作结果:
DELIMITER $$
CREATE PROCEDURE sp_move_hot_to_cold()
BEGIN
DECLARE v_rowcount INT DEFAULT 0;
DECLARE v_archived INT DEFAULT 0;
<p>-- 1. 尝试加锁
IF GET_LOCK('hot_to_cold_lock', 0) = 0 THEN
INSERT INTO migrate_log VALUES (NOW(), 'sp_move_hot_to_cold', 'SKIPPED: lock failed');
LEAVE proc_label;
END IF;</p><p>-- 2. 开始事务
START TRANSACTION;
INSERT INTO orders_archive
SELECT * FROM orders WHERE create_time </p><p>IF v_rowcount > 0 THEN
DELETE FROM orders WHERE create_time </p><p>COMMIT;</p><p>-- 3. 记录日志并释放锁
INSERT INTO migrate_log VALUES (NOW(), 'sp_move_hot_to_cold', CONCAT('Migrated ', v_archived, ' rows'));
DO RELEASE_LOCK('hot_to_cold_lock');
END$$
DELIMITER ;
</p>
注意:LIMIT 5000 必须显式写死,不能依赖变量(MySQL 存储过程中 LIMIT 不接受变量),且需确保 orders_archive 主键/唯一约束与源表一致,否则 INSERT 可能报错中断。
调用与调度的坑点
不要直接用 EVENT 每小时调用一次——如果某次执行耗时超过调度间隔,下次会堆积甚至并发。正确做法是:
- 用外部调度器(如 Linux
cron调用mysql -e "CALL sp_move_hot_to_cold();"),便于监控退出码 - 在过程末尾加
SELECT v_archived AS migrated_count;,方便脚本判断是否真有数据被处理 - 务必给
migrate_log表加created_at索引,否则日志写入本身成瓶颈
真正难的不是写完这个过程,而是确认归档后所有业务查询是否已剔除对旧表的隐式依赖——比如 ORM 中未更新的 @Table 注解,或报表 SQL 还在查 orders 全表。











