先查show variables like 'slow_query_log'是否为on,再确认long_query_time是否合理(如0.1秒)、slow_query_log_file路径权限正确;mysql 8.0默认关闭且set global不持久,须写入my.cnf并重启。

慢查询日志根本没出来?先查 MySQL 配置是否生效
Go 应用本身不生成慢查询日志,slow_query_log 和 long_query_time 是 MySQL 服务端配置项。只在 GORM 里开 LogMode(logger.Warn) 或调 db.Debug(),看到的只是“SQL 发了”,完全不知道它执行了多久、扫了多少行。
必须登录 MySQL 执行:SHOW VARIABLES LIKE 'slow_query_log';,确认值为 ON;再查 long_query_time 是否设得合理(开发环境建议 SET GLOBAL long_query_time = 0.1;)。
slow_query_log_file 路径通常在 /var/lib/mysql/xxx-slow.log,不是 Go 项目目录下;MySQL 8.0+ 默认关闭慢日志,SET GLOBAL 重启即失效,必须写进 /etc/mysql/my.cnf 的 [mysqld] 段并重启服务。
GORM 自带的 SlowThreshold 不起作用?两个参数必须同时配
只设 LogMode(logger.Warn) 不会触发慢查询判断逻辑——SlowThreshold 必须显式设置,否则 Warn 级别只报错和警告,不按耗时过滤。
正确做法是两者都配:
-
LogLevel设为logger.Warn或更低(Info也行,但慎用,易日志爆炸) -
SlowThreshold显式指定,比如200 * time.Millisecond
别信“加个 Debug() 就能看参数”——那只是临时升日志级别,参数仍不脱敏,且开发环境外不该用。
为什么 AfterFind 钩子抓不到慢 SQL?它根本不在执行路径上
AfterFind 在结构体反序列化完成后才触发,此时 SQL 已执行完毕、连接可能已归还池中,你拿不到真实耗时、SQL 原文、参数绑定值。
更关键的是,它对 Count、Raw、Session 查询完全无效,预加载子查询也可能绕过。
真正稳定捕获全部查询生命周期的,只有 Logger.Trace 方法:
- 它在 SQL 执行完、结果集已读取、连接未释放时调用
- 能拿到真实耗时、影响行数、原始 SQL 和错误
- 务必调用
fc()获取 SQL 和rowsAffected——直接读ctx或反射取不到参数值 - 避免在
Trace里做 I/O(如写文件、发 HTTP),否则会拖慢所有数据库操作
日志输出目标不一致?GORM 默认往 stdout 写,你的服务日志可能进了 rotator
GORM 默认日志输出到 os.Stdout,而你的主应用日志大概率被统一收集进 rotator 或其他日志系统。结果就是:SQL 错误或慢查询发生了,你在项目日志里找不到明细,只能靠猜。
必须把 GORM 日志重定向到和主应用一致的日志目标:
- 自定义
gorm.Logger实现,把Trace中的日志打到你的结构化 logger(如 zap、logrus)里 - 确保注入
traceID:从请求传入的ctx中提取,而不是塞进全局 DB 实例 - 生产环境保持
ParameterizedQueries: true(默认值),避免参数明文拼接带来的安全风险和性能开销
最容易被忽略的一点:日志输出目标必须和主应用日志一致。否则监控告警、链路追踪、问题回溯全都会断掉。











