mysql触发器不能定时清理过期数据,因其仅响应dml操作;应使用已启用event_scheduler的mysql event实现周期清理,它独立于事务运行且不干扰业务。

MySQL 触发器不能直接用于定时清理过期数据
触发器(TRIGGER)只响应 INSERT、UPDATE、DELETE 这三类 DML 操作,它没有“到了某时间就自动跑一次”的能力。想靠 BEFORE INSERT 或 AFTER DELETE 顺手把表里三个月前的记录删掉,逻辑上就不可靠——没写操作时,垃圾数据照旧堆积。
常见错误现象:ERROR 1442 (HY000): Can't update table 'xxx' in stored function/trigger because it is already used by statement which invoked this stored function/trigger,这是你在触发器里试图修改当前正在被操作的表导致的;更隐蔽的问题是,用触发器“借机”清理,会让业务 SQL 响应变慢、事务变长、锁住更多行。
- 真正适合定时清理的机制是 MySQL 事件(
EVENT),需确保event_scheduler已启用(查SHOW VARIABLES LIKE 'event_scheduler') - 如果业务层有写入高频但读取稀疏的特点,触发器式清理反而会把冷路径拖成热瓶颈
- 某些 ORM 会隐式执行
SELECT后跟UPDATE,若在UPDATE触发器里再删老数据,极易触发上述报错
用 MySQL EVENT 实现可控的周期清理
事件是唯一原生支持“按时间调度”的方案,它独立于连接和事务运行,不干扰业务 SQL 流程。但默认关闭,且权限独立——EVENT 权限必须显式授予用户。
典型使用场景:日志表 user_action_log 需保留最近 90 天数据,每天凌晨 2 点执行一次清理。
- 先确认开启:
SET GLOBAL event_scheduler = ON(或写进my.cnf的[mysqld]段) - 建事件前,建议先写好清理语句并加
LIMIT测试:DELETE FROM user_action_log WHERE created_at - 事件体中避免无限制
DELETE,否则可能锁表太久;可分批删,或改用PT-ARCHIVE等外部工具 - 注意时区:事件按 MySQL server 的
time_zone执行,不是系统时区;可用CONVERT_TZ()校准
PostgreSQL 怎么做等效处理
PostgreSQL 没有内置事件调度器,最轻量可靠的方案是配合系统 cron + psql。别信“用 pg_cron 扩展”,它虽能模拟事件,但需要编译安装、依赖额外进程、升级时易出兼容问题。
一个干净的做法是写个 shell 脚本,用 psql -c 直接执行带条件的 DELETE:
psql -U app_user -d mydb -c "DELETE FROM audit_log WHERE created_at <p>然后加到 crontab:</p><p><code>0 2 * * * /path/to/cleanup_audit.sh >> /var/log/pg-cleanup.log 2>&1</code></p>
- 必须加
LIMIT,否则大表删除可能堵塞 WAL、拖慢主库复制 - 脚本里推荐用
pg_is_in_recovery判断是否备库,避免在只读节点上误执行 - 如果表有外键引用或触发器,
DELETE可能意外触发级联动作,上线前务必在测试环境验证完整链路
清理逻辑该放在数据库还是应用层
放数据库(EVENT / cron + psql)更可靠:不依赖应用进程存活,不受部署拓扑影响,权限和执行上下文清晰。但代价是 DBA 需介入维护,SQL 变更需走 DB 变更流程。
放应用层(比如 Spring Boot 的 @Scheduled)看似方便,实际埋雷更多:
- 多实例部署时,多个进程同时执行清理,可能重复删或漏删
- 应用重启期间,清理任务就断了;而数据库事件只要服务起来就继续跑
- 清理失败不会自动告警,除非你额外搭监控;而 DBA 通常已对
event_status或 cron 日志有统一采集 - 如果清理涉及跨库、跨表关联,应用层拼 SQL 易出错,数据库内执行则事务和权限更可控
真正难的从来不是“怎么删”,而是删之前确认哪些数据真能删、删之后索引统计信息是否及时更新、归档备份是否覆盖了清理窗口——这些点,比选触发器还是事件重要得多。










