gorm 默认不打印完整sql是为了防止敏感数据泄露和减少日志体积,需通过自定义logger并重写logmode与info方法拼接sql.string()和sql.variables来实现参数化输出,且slowthreshold仅统计gorm层耗时,不包含网络及数据库锁等待。

为什么 GORM 默认不打印完整 SQL?
因为 GORM 的日志默认只输出简化版语句(比如 SELECT * FROM users WHERE id = ?),参数被占位符遮住了,你根本看不出实际执行的是什么。这不是 bug,是设计:避免敏感数据泄露、减少日志体积。但调试慢查询时,没真实参数等于盲人摸象。
常见错误现象:QueryLogger 配置后仍看不到参数值;或只看到 sql: no rows in result set 这类泛错误,无法定位哪条 SQL 超时/锁表。
- 必须显式启用
Config.Logger并传入自定义 logger,不能只靠DB.Debug() -
DB.Debug()只对单次调用生效,且不控制日志级别,容易漏掉后台定时任务里的慢 SQL - 若用
logrus或zap等结构化 logger,需包装成gormlogger.Interface,否则字段丢失
怎么让 GORM 打印带参数的原始 SQL?
核心是重写 LogMode 和 Info 方法,把 sql.String() 和 sql.Variables 拼起来。GORM v2 起不再直接暴露 sql 字段,得从 gormlogger.Config 里取 SlowThreshold 和 IgnoreRecordNotFoundError 控制粒度。
使用场景:排查分页查询 LIMIT ?,? 实际代入了 10000, 20 导致全表扫描;或确认 WHERE created_at > ? 是否真的用了索引(要看具体时间值是否在预期范围内)。
立即学习“go语言免费学习笔记(深入)”;
- 不要用
fmt.Printf直接打日志——会混入 goroutine ID 和时间戳,干扰 SQL 提取 - 参数值可能含换行或特殊字符,建议用
fmt.Sprintf("%v", v)而非fmt.Sprint(v)避免截断 - 生产环境务必关掉
LogMode(gormlogger.Info),改用LogMode(gormlogger.Warn)只打慢 SQL
简短示例:
logger := gormlogger.New(log.New(os.Stdout, "\r\n", 0), gormlogger.Config{
SlowThreshold: time.Millisecond * 200,
LogMode: gormlogger.Info,
})
db, _ = gorm.Open(mysql.Open(dsn), &gorm.Config{Logger: logger})
GORM 的 SlowThreshold 为什么常失效?
它只统计 GORM 层面的执行耗时,不包含网络往返、MySQL server 解析、锁等待等真实延迟。比如一条 SQL 在数据库里卡了 800ms(因行锁冲突),但 GORM 计时可能只显示 120ms——因为计时起点是 Go driver 发出请求前,终点是收到第一行结果后,中间的锁等待被算进 driver 底层了。
性能影响:设太低(如 10ms)会导致日志爆炸;设太高(如 5s)错过真实瓶颈。建议按业务接口 P95 响应时间的 1/3 设定,比如接口通常 300ms,就设 100ms。
- MySQL 的
long_query_time是服务端维度,和 GORM 的SlowThreshold不等价,两者要交叉验证 - 若用连接池,
DB.Stat().Idle突降 + 慢 SQL 日志突增,大概率是连接争抢,不是 SQL 本身慢 -
gorm.Preload的嵌套查询不会被单条计入SlowThreshold,得看整个事务总耗时
如何过滤掉健康检查和迁移 SQL?
服务启动时的 SELECT 1、CREATE TABLE IF NOT EXISTS 会刷屏,掩盖真正的问题 SQL。GORM 不提供内置过滤钩子,得在自定义 logger 的 Info 方法里硬过滤。
容易踩的坑:正则匹配 "SELECT 1" 会误杀 SELECT id FROM users WHERE status = 1;用 strings.HasPrefix(sql, "SELECT 1") 更安全。
- 迁移 SQL 通常含
CREATE、ALTER、DROP,但注意CREATE INDEX可能很慢,不该无条件过滤 - Kubernetes liveness probe 常走
/healthz接口,可在 handler 里临时db.Session(&gorm.Session{Logger: gormlogger.Discard})关闭日志 - 测试环境可加
if os.Getenv("ENV") == "test" { return }快速屏蔽
真实复杂点在于:有些“脏”SQL 是 ORM 自动生成的(比如 Preload 的 N+1),参数拼接后难以 grep;而手写的原生 Raw 查询又绕过 GORM 日志体系。这两类必须分开监控。











