最直接的方式是用 delete + where 清理过期测试数据,但切忌在大表上裸跑;本质是删行,需避免全表扫描与长事务阻塞。

用 DELETE + WHERE 清理过期测试数据最直接,但别在大表上裸跑
清理测试数据本质就是删行,DELETE FROM table_name WHERE created_at 这类写法没错,但执行前得看表大小和索引。没索引的 <code>created_at 字段会导致全表扫描,1000 万行可能卡住主库几小时。线上环境尤其要避开业务高峰,更不能在从库延迟时操作——否则主从复制会雪崩。
- 先确认
created_at有索引:SHOW INDEX FROM table_name WHERE Column_name = 'created_at' - 小表(DELETE;大表务必分批删,比如每次删 5000 行:
DELETE FROM table_name WHERE id IN (SELECT id FROM (SELECT id FROM table_name WHERE created_at - 避免用
DATE_SUB(NOW(), ...)在 WHERE 中计算,换成具体时间字符串(如'2024-04-01 00:00:00'),减少函数调用开销
用 TRUNCATE TABLE 清空整张测试表更快,但不可回滚且重置自增 ID
如果测试表只存临时数据、不需要保留任何历史,TRUNCATE TABLE test_log_202404 比 DELETE 快一个数量级,因为它不走事务日志、不逐行删、直接释放页。但代价很实在:它会重置 AUTO_INCREMENT 计数器,且无法被 ROLLBACK,也不触发 ON DELETE CASCADE 或触发器。
- 仅适用于完全无外键依赖、无业务连续性要求的纯测试表(如
test_user_tmp、mock_order_batch) -
TRUNCATE是 DDL,会隐式提交事务,执行前确保没有未提交的修改在同一个连接里 - MySQL 8.0+ 支持
TRUNCATE TABLE ... PARTITION,若表按时间分区(如PARTITION BY RANGE (TO_DAYS(created_at))),可精准清掉某几个分区,效率最高
定时任务靠 EVENT 最省心,但默认关闭且权限容易漏配
MySQL 原生 EVENT 能替代 shell 脚本或外部调度器,但很多人启用了 event_scheduler 却忘了给账号授 EVENT 权限,结果事件建了也不执行。另外,EVENT 默认不记录执行日志,出问题只能查 mysql.event 表和错误日志。
- 先开调度器:
SET GLOBAL event_scheduler = ON(重启失效,需写进my.cnf的[mysqld]段) - 创建者账号必须有
EVENT权限:GRANT EVENT ON database_name.* TO 'cleaner'@'%' - 事件体里慎用复杂子查询,建议封装成存储过程再调用,便于调试;例如:
CREATE EVENT ev_clean_test_data ON SCHEDULE EVERY 1 DAY DO CALL sp_clean_old_test_data()
误删后恢复靠备份和 binlog,但没提前开 binlog_format = ROW 就白搭
删错数据不是能不能恢复的问题,是“有没有条件恢复”的问题。如果 MySQL 没开 binlog,或 binlog_format 是 STATEMENT,那基于时间点的精确恢复基本不可能——因为 DELETE 语句本身不记录具体删了哪些行。
- 生产/测试混合环境务必开启 binlog:
log-bin = /var/lib/mysql/mysql-bin,并设为ROW格式 - 定期验证备份可用性,
mysqldump --single-transaction备份时加--skip-triggers避免测试触发器污染生产备份 - 清理脚本上线前,先在同结构的影子库跑一遍,用
SELECT替换DELETE看命中行数是否合理
真正麻烦的不是写一条删除语句,而是判断哪张表算“测试表”、谁有权删、删完会不会影响其他服务的缓存或下游 ETL 任务。很多“过期数据”其实被某个报表 SQL 硬编码引用着,删了就报空指针——这类耦合得靠代码扫描和上下游对齐,数据库层管不了。











