GORM 的 SlowThreshold 仅在 SQL 执行后记录慢日志,无法拦截;必须在 gorm:prepare_stmt 阶段介入,通过静态 SQL 结构分析(如无 WHERE、过多 JOIN)提前拦截高风险查询。

为什么不能直接用 GORM 的 SlowThreshold + Warn 日志
因为原生 SlowThreshold 只触发日志,不支持拦截、拒绝或上报;它发生在 SQL 执行完之后,无法阻止慢查询真正执行。你看到的是“已经慢了”,而不是“即将慢”。要实现「拦截」,必须在 SQL 构建完成、但尚未执行前介入——也就是 gorm:build_statement 或 gorm:prepare_stmt 阶段。
插件必须注册在 gorm:prepare_stmt 之后
这是 GORM v2 中唯一能稳定拿到完整 SQL 字符串(含参数占位符)且未执行的位置。早于它(如 gorm:build_statement)SQL 可能未拼完;晚于它(如 gorm:query)SQL 已发出,拦截失效。
- 注册时机必须是
db.Callback().PrepareStmt().After("gorm:prepare_stmt").Register(...) - 回调函数签名固定为
func(*gorm.DB),不能带 context 或 error - 从
tx.Statement.SQL.String()取 SQL,但注意:此时参数尚未绑定,只有?占位符 - 想判断是否可能慢,只能靠规则:比如
SELECT无WHERE、JOIN超过 3 张表、ORDER BY缺索引字段等——这需要你额外做 SQL 解析或正则匹配
一个可运行的慢 SQL 拦截插件示例
下面代码只拦截「无 WHERE 条件的 SELECT」和「超过 4 张表 JOIN」两种典型场景,支持白名单表跳过校验:
type SlowSQLInterceptor struct {
allowList map[string]bool
maxJoin int
}
func (p *SlowSQLInterceptor) Name() string {
return "slow_sql_interceptor"
}
func (p *SlowSQLInterceptor) Initialize(db *gorm.DB) error {
db.Callback().PrepareStmt().After("gorm:prepare_stmt").Register("intercept:slow_sql", func(tx *gorm.DB) {
if tx.Error != nil {
return
}
sql := tx.Statement.SQL.String()
if !strings.HasPrefix(strings.TrimSpace(sql), "SELECT") {
return
}
tableName := tx.Statement.Table
if tableName != "" && p.allowList[strings.ToLower(tableName)] {
return
}
// 简单检测 JOIN 数量(粗略,不覆盖子查询)
joinCount := strings.Count(strings.ToUpper(sql), " JOIN ")
if joinCount > p.maxJoin {
tx.Error = fmt.Errorf("too many joins (%d > %d): %s", joinCount, p.maxJoin, sql[:min(len(sql), 100)])
return
}
// 检查是否有 WHERE(忽略 WHERE 1=1 这类无效条件)
sqlUpper := strings.ToUpper(sql)
hasWhere := strings.Contains(sqlUpper, " WHERE ") || strings.Contains(sqlUpper, " WHERE\n")
if !hasWhere || regexp.MustCompile(`(?i)\bWHERE\s+1\s*=\s*1\b`).MatchString(sql) {
tx.Error = fmt.Errorf("missing valid WHERE clause in SELECT: %s", sql[:min(len(sql), 100)])
}
})
return nil
}
func NewSlowSQLInterceptor(maxJoin int, allowList []string) *SlowSQLInterceptor {
m := make(map[string]bool)
for _, t := range allowList {
m[strings.ToLower(t)] = true
}
return &SlowSQLInterceptor{
allowList: m,
maxJoin: maxJoin,
}
}
使用方式:db.Use(NewSlowSQLInterceptor(4, []string{"logs", "metrics"}))
注意:这不是性能监控,而是 SQL 合规性拦截
这个插件不测量耗时,也不依赖 context 或计时器——它只做静态 SQL 结构检查。真正的「慢」由数据库执行时决定,而这里拦截的是「大概率会慢」的写法。上线前务必关掉,否则会影响所有 SELECT 查询路径;它适合放在开发/测试环境做 CI 检查,或灰度阶段强制规范 SQL 写法。正则匹配 JOIN 和 WHERE 很粗糙,复杂 SQL(含子查询、UNION、CTE)会漏判,生产环境若需精确拦截,应结合 MySQL 的 performance_schema 或代理层(如 ProxySQL)做运行时分析。


















