slowquerymiddleware 不能直接复用 django 思路,因 go 的 database/sql 无统一查询拦截机制,需通过规范调用方式(如 service 层封装 + context 透传 + queryrowcontext)+ 手动计时 + 结构化日志(含 traceid)实现慢 sql 拦截与精准归因。

SlowQueryMiddleware 不能直接复用 Django 思路
Fiber 是 Go 语言框架,没有 Django 那样的 django.db.connection.queries 或 CursorWrapper 机制。Go 的数据库操作(如 database/sql)本身不提供统一的查询拦截钩子,更不会自动关联 HTTP 请求上下文。想在中间件里“拦截慢 SQL”,必须把数据库调用显式接入请求生命周期——不能靠包装 cursor,而要靠规范调用方式 + 手动计时。
必须把 DB 查询封装成带 ctx 的函数并统一入口
所有涉及数据库的操作,不能直接写 db.QueryRow("SELECT ..."),必须走一个受控的 service 层函数,例如 userRepo.GetByID(ctx, id)。这个 ctx 就是你在中间件中注入的、带超时和 trace ID 的 context。
- 中间件中调用
ctx, cancel := context.WithTimeout(c.Context(), 5*time.Second),然后把ctx传给后续 handler - handler 再把
ctx透传给 repo 方法,repo 内部用db.QueryRowContext(ctx, ...)替代裸调用 - 在 repo 方法开头打点:
start := time.Now();执行完后计算time.Since(start) - 若耗时超过阈值(如 200ms),就写日志:包含
c.Path()、c.Method()、SQL 摘要(可截取前 100 字)、参数(需脱敏)、耗时 ms
别在中间件里直接测 DB 耗时,否则会漏判
中间件的 c.Next() 只控制 handler 执行,但 handler 内部可能启动 goroutine 异步查库、或调用第三方 client,这些都不会被 c.Next() 的耗时统计覆盖。常见错误:
- 在中间件里用
time.Since()包住c.Next(),以为这就是“接口总耗时”,其实它不含异步 DB 调用时间 - 把 slow-log 逻辑写在中间件里,但 repo 层仍用
db.QueryRow()(非 Context 版),导致根本没触发 timeout,也拿不到真实执行时间 - 日志里只记了 “/api/user GET took 12ms”,但实际慢在后续异步发消息或调第三方 API,误导排查方向
生产环境慢查询记录必须带 traceID 和结构化字段
单纯打印字符串日志无法聚合分析。你应该用结构化日志库(如 zerolog),在中间件初始化时注入 traceID,并让所有 DB 日志共享该 ID:
logger := zerolog.Ctx(c.Context()).With().Str("trace_id", traceID).Logger()
// 然后在 repo 里:
logger.Warn().Dur("duration", time.Since(start)).Str("sql", sqlShort).Interface("params", redactedParams).Msg("slow_db_query")
这样就能在日志系统里按 trace_id 关联整个请求链路,确认是 DB 慢,还是下游服务慢,还是 GC 卡顿。
真正难的不是加一行计时,而是让所有 DB 调用都走同一套带 ctx 的路径——这需要团队约定、CR 把关、工具链检查,否则很快就会有人绕过。











