应使用 time.Ticker 启动常驻清理协程,避免 time.Sleep 轮询导致的时间漂移和 panic 停摆;需结合 context 控制生命周期、recover 捕获异常、WHERE+LIMIT 保障 SQL 安全。

用 time.Ticker 启动常驻清理协程,别用 time.Sleep 轮询
直接在 main() 或服务初始化时启一个 goroutine,用 time.Ticker 控制节奏最稳妥。用 time.Sleep 模拟定时容易 drift(比如清理耗时 800ms,再 Sleep 1s,实际间隔就变成 1.8s),还可能因 panic 导致后续完全停摆。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 创建
ticker := time.NewTicker(10 * time.Minute),周期按业务容忍度设(如 token 过期 24h,可设 1h 清一次) - 在 goroutine 中用
select监听ticker.C和ctx.Done(),确保服务关闭时能退出 - 每次触发前先检查是否已关机(
if ctx.Err() != nil),避免清理中途被中断留下脏数据 - 清理逻辑本身要加
recover(),防止某条 SQL 或 JSON 解析 panic 撞垮整个 ticker
清理 SQL 要加 WHERE 条件和 LIMIT,别全表扫
过期字段通常是 updated_at 或 expires_at,但直接 DELETE FROM sessions WHERE expires_at 在大表上会锁表、拖慢主业务。尤其用 MySQL InnoDB 时,没索引的 <code>WHERE 条件等于全表扫描。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 确保过期字段有索引:
CREATE INDEX idx_sessions_expires ON sessions(expires_at) - 加
LIMIT分批删,比如每次只删 1000 条:DELETE FROM sessions WHERE expires_at - 用
rowsAffected, err := tx.Exec(...)判断是否还有数据可删,rowsAffected == 0就 break,避免空跑 - 别用 ORM 的批量 delete(如 GORM
Delete(&Session{}, "expires_at < ?", time.Now())),它默认不带 LIMIT,且生成的 SQL 可能绕过索引
用数据库原生 TTL(如 MongoDB)或 Redis 过期机制,比手写更可靠
如果数据天然适合键值存储(如用户 session、验证码、临时 token),优先交给存储层自动过期。自己维护定时任务多一层故障点:服务重启丢失状态、ticker 初始化失败、DB 连接断开后 silent fail。
在 Go 中使用 google/wire 实现编译时依赖注入——wire.NewSet、wire.Build、wire.Bind(接口→实现)、wire.Struct、wire.Value、wire.Interface
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- MongoDB:建
expireAfterSeconds索引,db.sessions.createIndex({ "expires_at": 1 }, { expireAfterSeconds: 0 }),写入时设expires_at: new Date(Date.now() + 24*60*60*1000) - Redis:用
SET key value EX 86400,或对已存在 key 执行EXPIRE key 86400,不用查、不用删 - PostgreSQL:虽无原生 TTL,但可用
pg_cron扩展跑定期 job,比应用层 ticker 更隔离 - Golang 里只管写,别管删——这才是“清理”的终极解法
日志和监控必须带关键上下文,否则线上出问题找不到根因
只打 log.Println("cleaned 123 expired records") 没用。等某天发现清理变慢或漏删,你根本不知道是 DB 延迟升高、SQL 走了全表、还是某个特定 user_id 的数据异常阻塞。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 每轮清理记开始/结束时间,计算耗时:
start := time.Now(); ...; log.Printf("cleanup done in %v, deleted %d", time.Since(start), n) - 记录影响行数、最大 ID(用于排查是否卡在某段数据)、错误信息(如
err.Error()而非只打 “delete failed”) - 上报指标到 Prometheus:
cleanup_duration_seconds{job="session"} 12.34、cleanup_deleted_total{job="session"} 1000 - 避免在循环里每删一条就打日志——高频日志会拖垮磁盘 IO,聚合后统一打
真正麻烦的不是写定时任务,而是当某天凌晨三点发现清理延迟 2 小时、Redis 内存涨到 95%、而日志里只有两行 “started” 和 “done” —— 那时候你才意识到,当初少打的那几个字段,现在得花三小时翻代码补。

















