必须用logger.Default.LogMode(logger.Info)在gorm.Config.Logger中配置,才能输出SQL语句;LogMode是运行时开关,可随时通过db.Logger.LogMode(logger.Silent)关闭。

开启 GORM 的 SQL 日志输出(基础配置)
GORM 默认不打印 SQL,必须显式启用日志器。最直接的方式是用 Config.Logger 配合 logger.Default.LogMode():
import (
"gorm.io/gorm/logger"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
<p>db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
})
logger.Info 会输出所有执行的 SQL、参数、耗时和行数;如果只想看事务相关 SQL,用 logger.Warn 也行(它只打带 Begin/Commit/Rollback 的语句),但容易漏掉事务内普通查询。
注意:LogMode 是运行时开关,不是初始化后就一劳永逸——后续可随时调用 db.Logger.LogMode(logger.Silent) 关闭。
为什么事务里查不到 INSERT/UPDATE 的 SQL?
常见现象:开了日志,但只看到 SELECT,INSERT 或 UPDATE 没输出,尤其在事务块里。
根本原因有两个:
- GORM 在事务中默认复用同一个
<em>gorm.DB</em>实例,而日志器绑定在该实例上;如果你在事务里用了db.WithContext(ctx).Create(...)这类链式调用,但没把日志器透传过去,新生成的gorm.DB实例可能没继承日志配置 - 更隐蔽的是:某些中间件或封装层(比如自定义
DB().Transaction()包装函数)手动创建了子*gorm.DB,却忘了复制Logger
解决办法:
- 所有事务操作统一走
db.Transaction(func(tx *gorm.DB) error { ... }),不要自己db.Begin()后再拼tx.Create() - 如果必须手动管理事务,记得给
tx显式设置日志器:tx.Session(&gorm.Session{Logger: db.Logger}) - 检查是否误用了
db.Debug():它只对当次调用生效,不会影响事务内后续操作
区分事务边界与嵌套事务的 SQL 标记
GORM 不支持真正嵌套事务(底层数据库也不支持),所谓“嵌套”只是保存点(SAVEPOINT)。但日志里不会自动标注 SAVEPOINT 名字,容易混淆哪段 SQL 属于哪个逻辑块。
你可以手动加注释来区分:
db.Transaction(func(tx *gorm.DB) error {
// 注释会被原样输出到 SQL 日志里
tx.Exec("/* user_service_create */ SELECT ...")
return tx.Create(&user).Error
})
这样日志里会出现类似:
/<em> user_service_create </em>/ INSERT INTO <code>users ...
另外,COMMIT 和 ROLLBACK 本身一定会被记录,但 SAVEPOINT 和 RELEASE SAVEPOINT 是否出现,取决于你是否用了 tx.SavePoint() —— 它们不会自动触发,必须显式调用。
生产环境怎么安全地开 SQL 日志?
直接设 LogMode(logger.Info) 会打满磁盘,尤其高并发下每秒几百条 SQL。
可行做法:
- 仅在调试环境或特定请求路径开启:用
context.WithValue传一个 flag,进事务前动态切换日志等级 - 用第三方日志器(如
zap)替换默认 logger,实现采样(例如每 100 条 SQL 打 1 条)或按 SQL 类型过滤(只打INSERT/UPDATE) - 最小侵入方案:改用
db.Session(&gorm.Session{Logger: logger.Discard})全局关闭,只在需要调试的代码块里临时换回带日志的 session
事务 SQL 日志的关键不在“能不能看到”,而在“看到的是否完整且归属清晰”——手动加注释、避免子 DB 实例丢失 logger、以及理解 GORM 的事务代理机制,比调一个配置更重要。


















