GORM需显式配置logger才能打印SQL,正确做法是构造带Config的logger实例并传入gorm.Open;日志中问号是预处理占位符,真实参数单独显示,ToSQL可生成完整SQL但不执行。

怎么让GORM打印出它执行的SQL语句
默认情况下GORM不会输出SQL,必须显式开启日志。最直接的方式是在初始化*gorm.DB时配置logger:用gorm.Logger的Config控制日志级别和输出目标。
常见错误是只调用logger.Default.LogMode(logger.Info)却没把logger传进gorm.Open——这完全无效。正确做法是构造一个带配置的logger实例,再传进去:
newLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
logger.Config{
SlowThreshold: time.Second, // 慢SQL阈值
LogLevel: logger.Info, // 输出SQL、行数、耗时、错误
Colorful: true, // 启用颜色
},
)
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: newLogger,
})
-
LogLevel设为logger.Info才输出SQL;logger.Silent会关闭所有日志 - 如果只关心SQL本身(不想要时间戳、行号等),可以把
io writer换成自定义io.Writer做过滤 - 在测试环境用
os.Stdout方便,但生产环境务必关掉或重定向到文件,避免性能损耗
为什么看到的SQL里有问号而不是真实参数
GORM默认使用预处理语句(prepared statement),所以日志中显示的是带?占位符的SQL,真实参数在下一行以RowsAffected或Args形式单独打印。这不是bug,是数据库驱动层的行为。
如果你需要拼接后的完整SQL(比如复制到MySQL客户端直接执行),不能依赖日志,得手动构建:
立即学习“go语言免费学习笔记(深入)”;
sql, vars := db.ToSQL(func(tx *gorm.DB) *gorm.DB {
return tx.Where("age > ?", 18).Find(&users)
})
// sql 是 "SELECT * FROM users WHERE age > ?"
// vars 是 []interface{}{18}
-
db.ToSQL是GORM v2.2.5+才支持的方法,旧版本需用session.Debug().First(&u)配合捕获日志 - 注意
ToSQL不执行SQL,只生成;且不支持事务内嵌套查询的完整还原 - 敏感字段(如密码、token)在
vars里仍是明文,切勿直接打日志
调试时SQL没打印出来,可能卡在哪几个地方
不是所有GORM操作都会触发SQL日志。以下情况容易漏看:
- 链式调用中用了
Session但没透传Logger:新session默认用logger.Default,需显式指定&gorm.Session{Logger: db.Logger} - 用了
Count、FirstOrInit等方法,但日志级别设成了logger.Warn——这些操作在Info级才输出 - 数据库连接失败或超时,GORM先报
failed to connect这类错误,SQL日志根本不会触发 - 用了
Debug()方法但写在了Where之后:顺序必须是db.Debug().Where(...).Find(...),否则无效
线上环境如何安全地临时开启SQL日志
线上不能长期开Info级日志,但排查问题时又需要。推荐用动态开关+条件日志:
// 全局logger支持运行时切换级别
db.Logger = db.Logger.LogMode(logger.Error) // 默认只打错误
<p>// 接口收到特定header(如 X-Debug-SQL: true)时临时升为Info
if r.Header.Get("X-Debug-SQL") == "true" {
db.Logger = db.Logger.LogMode(logger.Info)
defer func() { db.Logger = db.Logger.LogMode(logger.Error) }()
}
- 不要用
os.Setenv或全局变量控制日志级别——goroutine不安全,容易污染其他请求 - 更稳妥的做法是为每个请求创建独立
*gorm.DB(用Session),只对该session开启日志 - 即使临时开启,也要限制单次最多打印10条SQL,避免日志刷爆磁盘
真正难的不是让SQL出来,而是确认它和你预期的一致——比如Preload是否触发N+1,Joins有没有丢条件,这些得结合执行计划(EXPLAIN)一起看,光靠日志不够。


















