应使用 time.ticker 启动独立 goroutine 执行周期性健康检查,避免 time.afterfunc 递归调用;需捕获 panic、设置查询超时、显式关闭 rows、仅执行只读轻量 sql,并按失败类型分级告警。

用 time.Ticker 启动长期运行的巡检协程,别用 time.AfterFunc 递归调用
定时巡检本质是周期性执行 SQL 查询并校验结果,必须保证任务不因单次失败而中断。用 time.AfterFunc 递归调度看似简洁,但一旦某次执行 panic 或未 recover,后续调度就彻底丢失。
正确做法是启动一个独立 goroutine,用 time.Ticker 驱动循环:
func startHealthCheck(db *sql.DB, interval time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for range ticker.C {
if err := runOneCheck(db); err != nil {
log.Printf("health check failed: %v", err)
// 不 return,继续下一轮
}
}
}
-
time.Ticker自带误差补偿,比反复time.Sleep更稳定 - 务必在 goroutine 内处理 panic,否则整个程序可能被拖垮:
defer func() { if r := recover(); r != nil { log.Printf("panic in check: %v", r) } }() - 不要在
for循环里直接db.QueryRow后不.Scan—— 连接不会释放,很快耗尽
巡检 SQL 必须带超时控制,且显式调用 rows.Close()
数据库响应慢或网络抖动时,没超时的查询会卡死整个 ticker 协程,导致后续检查全部堆积。Go 的 database/sql 默认不设查询超时,必须手动加。
两种可靠方式:
- 用
context.WithTimeout包裹查询:ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second); defer cancel(); row := db.QueryRowContext(ctx, "SELECT 1") - 或配置连接池级超时(如 MySQL DSN 加
timeout=5s&readTimeout=5s&writeTimeout=5s),但不如上下文精准
即使查询成功,也必须显式 rows.Close() 或 row.Scan()(后者内部会关);否则连接长期占用,db.Stats().InUse 会持续上涨,最终触发 sql.ErrConnDone。
避免在巡检中写入业务表,优先查 information_schema 或执行轻量语句
巡检任务不是运维脚本,不能随意 UPDATE 或 INSERT。常见错误是想“自动修复”而直接操作数据,这极易引发一致性问题或锁表。
安全的巡检动作只应包括:
- 查
SELECT 1确认连通性 - 查
information_schema.TABLES核对关键表是否存在、行数是否突变为 0 - 查
SHOW SLAVE STATUS(MySQL)或pg_stat_replication(PostgreSQL)确认主从延迟 - 执行
SELECT pg_is_in_recovery()判断 PostgreSQL 是否处于只读状态
所有语句必须是只读、无锁、毫秒级完成的。任何涉及 JOIN 大表、GROUP BY 或子查询的操作,都该从巡检列表里剔除。
巡检结果要区分 “失败” 和 “不可达”,用不同日志级别和上报通道
网络闪断导致连接 refused 是临时态,应该打 WARN 并静默重试;而查到主从延迟 > 60s 是确定性异常,需立刻 ERROR 并发告警。
建议分三级处理:
- 连接建立失败(
dial tcp: i/o timeout)→ 记WARN,不告警,等下次 tick - SQL 执行超时或返回非预期值(如延迟 > 60s)→ 记
ERROR,触发企业微信/钉钉机器人推送 - 连续 3 次同类型失败 → 上报 Prometheus
db_health_failed_total{type="replica_lag"}并升为 P0 工单
最容易被忽略的是:别把 context.DeadlineExceeded 和业务逻辑错误混在一起处理。前者是基础设施层问题,后者可能是 SQL 写错了——它们的应对策略完全不同。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











