必须配置SlowThreshold并启用Info及以上日志级别,将GORM日志接入主日志系统并透传traceid,再结合EXPLAIN分析执行计划、连接池与上下文超时设置,才能准确定位慢查询真实瓶颈。

直接开启 GORM 的慢查询日志,配合上下文追踪和数据库执行计划分析,才能准确定位慢在哪一环——不是所有“慢”都出在 SQL 本身。
配置 SlowThreshold 并启用日志级别
GORM 默认不记录慢查询,必须显式设置阈值并选择合适的日志等级。低于 SlowThreshold 的 SQL 不会触发慢查询标记,但可能仍需关注;设太高会漏掉真实瓶颈。
-
SlowThreshold建议设为200 * time.Millisecond(开发环境)或500 * time.Millisecond(生产环境),避免被网络抖动误判 - 日志级别不能用
logger.Silent或logger.Error,必须是logger.Info或更高,否则慢查询不会输出 - 确保
Logger实例传给了gorm.Config,而不是只调用了db.Logger.LogMode()(后者仅影响当前 db 实例,且 v2 中已弃用)
把 GORM 日志接入项目主日志系统
如果 GORM 日志还打在 os.Stdout 或独立文件里,你就没法关联请求上下文、查不到慢查询前后的业务逻辑,等于白开。
- 自定义
logger.Interface实现,把Print方法重定向到你用的结构化日志库(如zap、zerolog) - 在
Print中提取sql、rows、dur(ms)、err等字段,打成 JSON 结构日志,并带上traceid(从context中提取,需在调用链中透传) - 避免在
Print里做耗时操作(如格式化时间、拼接字符串),否则日志本身会拖慢 DB 调用
区分“慢 SQL”和“慢执行”:用 EXPLAIN 验证执行计划
GORM 打印出的慢日志只告诉你“这句 SQL 花了多久”,但不告诉你“为什么慢”。同一句 SQL 在不同数据量、索引状态、连接数下表现可能天差地别。
- 复制日志里的
sql字段内容(注意替换问号参数为实际值,或用PreparedStmt: true配置让 GORM 输出带参数的 SQL) - 在数据库客户端中执行
EXPLAIN FORMAT=JSON [your_sql],重点看key(是否命中索引)、rows(扫描行数)、type(访问类型,ALL是全表扫描) - 特别注意 GORM 自动生成的 JOIN 查询:多级
Preload可能导致笛卡尔积,EXPLAIN里rows会暴增
检查连接池与上下文超时是否掩盖真实问题
有时候你看到的“慢查询”其实是连接等待或上下文提前取消造成的假象,SQL 根本没执行完。
- 确认
db.SetMaxOpenConns()和db.SetMaxIdleConns()是否足够,高并发下连接耗尽会导致请求卡在acquireConn阶段,GORM 日志里显示的“慢”其实是排队时间 - 所有 DB 调用必须带
context.WithTimeout(),否则慢查询会阻塞整个 goroutine;但如果超时设得太短(如100ms),可能频繁截断正常查询,让你误以为 SQL 本身慢 - 观察日志中是否出现
context deadline exceeded错误——这说明问题不在 SQL,而在调用方超时策略或下游依赖
真正难排查的慢查询,往往藏在预加载嵌套过深、软删除字段未建索引、或时间范围查询没走索引这些细节里。GORM 日志只是入口,别停在那行 sql 字段上。


















