Go 应用需在应用层主动拦截 SQL 执行并测量耗时,利用 ORM 钩子或封装 database/sql 方法,结合上下文与参数记录结构化慢查询日志,以支撑精准告警与根因分析。

Go 框架中如何捕获慢查询 SQL 语句
慢查询日志不是数据库自动“推”给 Go 应用的,得靠应用层主动拦截和测量。主流 ORM(如 gorm)和原生 database/sql 都支持钩子(Hook)或中间件机制,核心是围绕 QueryContext、ExecContext 等方法做耗时统计。
关键点:必须用上下文传入超时控制,并在执行前后打点。别依赖数据库自身的 slow log,那无法关联 Go 请求链路、参数、HTTP 路径等上下文信息。
-
gorm推荐用logger.Interface实现自定义日志器,在LogMode(gorm.Info)基础上重写Trace方法,判断elapsed > threshold(比如 200ms)才记录 - 原生
database/sql可包装sql.Conn或用sql.Driver代理,但更轻量的做法是封装sql.DB的QueryRowContext等方法,统一加time.Since(start) - 务必记录
stmt.String()(预编译语句)或query(原始 SQL),避免只记 placeholder 导致无法分析真实模式
为什么不能直接用 MySQL 的 slow_query_log 做告警
MySQL 自身的慢日志只存执行时间、锁等待、扫描行数,但缺失 Go 应用侧的关键维度:调用方 IP、HTTP 路径、用户 ID、trace_id、参数值(如 user_id = ? 后的真实值)、goroutine ID。没有这些,告警来了也难定位是哪个接口、哪个用户、哪类数据触发的。
更实际的问题是:MySQL 日志落盘有延迟(默认异步刷盘),且日志格式不统一(5.7 vs 8.0 字段不同),解析成本高;而 Go 层拦截是实时、结构化、可扩展字段的。
立即学习“go语言免费学习笔记(深入)”;
- MySQL
long_query_time是按服务端执行时间算的,不含网络传输、连接池等待、Go 层反序列化开销——这部分可能占大头 - 某些慢查询是因参数导致(如
offset过大),但 MySQL 日志里只显示LIMIT ?,?,看不到真实 offset 值 - 开启
log_queries_not_using_indexes会误报大量合理查询,干扰告警信噪比
生成自动化告警分析报告的最小可行结构
报告不是堆日志,而是聚焦“可行动项”。一个有效告警至少包含三块:触发条件(什么慢了)、上下文快照(谁在哪调的)、根因线索(为什么慢)。用 Go 生成 JSON/Markdown 报表即可,无需上 ELK。
示例字段建议(全部结构化,便于后续聚合):
{
"timestamp": "2024-06-12T10:23:45Z",
"duration_ms": 1240.3,
"sql": "SELECT * FROM orders WHERE user_id = ? AND status = ? ORDER BY created_at DESC LIMIT ?,?",
"args": [12345, "paid", 1000, 20],
"http_path": "/api/v1/orders",
"client_ip": "10.20.30.40",
"trace_id": "abc123-def456",
"explain_output": "{'type': 'index', 'key': 'idx_user_status', 'rows': 82400}"
}-
args必须记录,否则无法判断是“分页越深”还是“用户数据异常膨胀” -
explain_output建议在慢查询发生后,用同一连接执行EXPLAIN FORMAT=JSON ...获取,注意加context.WithTimeout防卡死 - 避免在告警生成阶段做复杂聚合(如“本周 TOP 5 慢 SQL”),那该由后台定时任务做;告警本身只输出原子事件
告警发送前必须做的过滤与降噪
刚上线时容易被自己的测试请求、健康检查、定时任务刷屏。真正的慢查询告警,要过三道筛子。
- 排除已知低风险模式:SQL 含
/*+ IGNORE_INDEX */、FORCE INDEX、或匹配^SELECT COUNT\(DISTINCT(这类本就慢,应优化而非告警) - 同 SQL + 同参数组合 5 分钟内只发一次(用
sync.Map存hash(sql+args) → lastSent),防抖动误报 - 检查是否真需要人工介入:如果
duration_ms > 5000且explain.rows > 100000,才走企业微信/钉钉;其余走内部日志平台并打标level=warn
最常被忽略的是:没区分“偶发毛刺”和“稳定劣化”。建议对每个 SQL 模板维护滑动窗口(最近 10 次)的 P95 耗时,仅当当前值 > 1.5× 历史 P95 且持续 3 次才触发——这比固定阈值靠谱得多。


















