应包装driver.conn实现在prepare、query、exec中统一打点,用time.now()测端到端耗时,阈值建议200ms;须记录脱敏sql、参数、调用栈及连接id,避免仅包装db.query导致漏测网络与执行时间。

如何用 sql.DB 配合 sql.Driver 拦截慢查询
Go 标准库的 database/sql 本身不提供查询耗时钩子,必须在驱动层或连接池之上做拦截。最直接有效的方式是包装 sql.Conn 或使用支持上下文和钩子的第三方驱动(如 pgx/v5 或 mysql 的自定义 Connector)。但若坚持用原生 sql.DB,推荐在调用 QueryContext、ExecContext 等方法前手动打点:
- 用
time.Now()记录起始时间,defer中计算耗时并判断阈值(例如 > 500ms) - 注意:不能只包装
DB.Query,它不接受context.Context,无法感知超时或取消,应统一迁移到带Context的变体 - 日志中至少记录:
SQL(需脱敏参数)、args(可选)、duration、error(如果有)
用 pgx/v5 的 ConnConfig.Tracer 实现精准慢查询捕获
pgx 是目前 Go 生态中对 PostgreSQL 慢查询监控支持最完善的驱动。它的 Tracer 接口允许你在每个查询生命周期的关键节点(如 QueryStart、QueryEnd)插入逻辑,比手动包装更可靠、无遗漏:
- 实现
pgx.Tracer接口,重点关注QueryStart存储开始时间,QueryEnd计算耗时并判断是否超过slowThreshold - 注意
QueryEnd的err参数可能为nil,但查询仍可能慢;不要只依赖错误触发日志 - 避免在
Tracer中做阻塞操作(如同步写磁盘日志),建议发到chan或用异步 logger(如zerolog的LevelWriter) - 示例关键片段:
tracer := &myTracer{slowThreshold: time.Second}<br>config := pgx.ConnConfig{Tracer: tracer}
MySQL 场景下用 github.com/go-sql-driver/mysql 的 interceptor 替代方案
官方 MySQL 驱动不原生支持 Tracer,但 v1.7+ 引入了实验性 interceptor 机制(通过 mysql.RegisterDialContext + 自定义 Dialer),可用于包裹连接行为。不过更稳妥的做法是升级到 github.com/Planetscale/vtprotobuf 或直接换用 github.com/sjclijie/go-mysql 这类可插拔设计的驱动。如果必须用原驱动:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 在
sql.Open后,对返回的*sql.DB调用SetMaxOpenConns和SetConnMaxLifetime,防止慢查询堆积拖垮连接池 - 用
DB.Stats()定期检查WaitCount和WaitDuration,这两个指标突增往往预示着慢查询已开始阻塞后续请求 - 不要试图 patch
mysql.MySQLDriver的内部方法——它未导出且版本间易断裂
日志内容里哪些字段不能省,否则查不出根因
慢查询日志如果只记「花了 2.3s」,基本等于没记。真正能定位问题的字段必须包含:
-
query:原始 SQL(注意:不是fmt.Sprintf拼接后的,而是驱动实际发送的,含占位符如$1或?) -
args:参数值(敏感数据需脱敏,如手机号替换为"138****1234",但要保留类型和长度特征) -
stack:调用栈(用debug.PrintStack()或runtime.Caller获取上三层,定位到具体业务函数) -
conn_id或pid:PostgreSQL 可取pg_backend_pid(),MySQL 可执行SELECT CONNECTION_ID(),用于关联数据库端pg_stat_activity或SHOW PROCESSLIST
漏掉其中任意一项,排查时大概率要重新加日志、等复现、再上线——而慢查询往往不具备稳定复现条件。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










