dryrun模式不能测性能,仅生成sql字符串;其输出的sql因占位符格式、软删除条件、字段选择等与真实执行存在偏差,需结合真实查询和explain分析。

DryRun 模式本身不能测性能,它只生成 SQL 字符串,不走网络、不触发锁、不查索引——拿它 benchmark 是在测 GORM 的字符串拼接速度,不是数据库响应。
为什么 DryRun 输出的 SQL 不能直接用于 EXPLAIN
DryRun 返回的 stmt.Statement.SQL.String() 和 stmt.Vars 看似“真实”,但有几处关键偏差:
- PostgreSQL 驱动下,DryRun 输出占位符是
@name,真实执行时是$1;MySQL 下 DryRun 是?,但部分配置下可能被驱动转成命名参数——EXPLAIN 不认这些占位符格式,会报错或忽略参数绑定 -
db.Unscoped().Where("deleted_at IS NULL")在 DryRun 中仍会输出该条件,但若结构体没定义DeletedAt字段,真实执行时 GORM 可能自动剔除该 WHERE,导致 SQL 不一致 -
db.Select("id, name")在 DryRun 中正确,但若后续调用Find(&users)且users是未导出字段或含gorm:"-"标签的结构体,GORM 真实执行时可能 fallback 到SELECT *
如何用 DryRun + 真实执行组合做有效审计
必须把 DryRun 当作“SQL 校验前置步骤”,而非“替代执行”。实际流程要闭环:
- 构造和线上完全一致的链式调用(包括
Session、Scopes、Joins、Limit),先跑一遍db.Session(&gorm.Session{DryRun: true}),拿到SQL.String()和Vars - 用同一组参数,发起真实查询:
db.WithContext(ctx).Exec(sqlStr, vars...)或db.Raw(sqlStr, vars...).Scan(&result),并记录耗时与RowsAffected() - 把
SQL.String()按目标库语法标准化(如 PostgreSQL 替换@name→$1),再粘贴到 psql/pgAdmin 执行EXPLAIN (ANALYZE, BUFFERS) - 开启
PrepareStmt: true配置,否则 DryRun 看到的是文本协议 SQL,而真实环境走预编译——两者执行计划可能完全不同
GORM 场景下 DryRun 审计容易漏掉的点
审计不是只看主查询,GORM 的隐式行为会让 DryRun “看不见”真正执行的语句:
-
AfterFind钩子里如果调用了db.First(),DryRun 不会捕获这条额外 SQL;必须在钩子中也加Session{DryRun: true}单独抓 - 批量操作如
FindInBatches,DryRun 只返回单批次 SQL,但真实执行会循环 N 次——需检查批次大小是否合理,避免 N+1 问题被掩盖 - 软删除字段未加
index时,DryRun 生成的WHERE deleted_at IS NULL看似正常,但真实执行会全表扫描;得结合EXPLAIN的Seq Scan提示判断 - 事务内嵌套调用(如
tx.Create()后又tx.Model().Updates())在 DryRun 中无法复现事务上下文对锁和 MVCC 的影响
真正卡住服务的从来不是 SQL 生成那几十微秒,而是索引缺失、JOIN 顺序错误、参数化失效导致计划缓存污染——DryRun 帮你守住第一道关:SQL 别写错;后面的事,得靠真实执行 + 数据库原生分析工具来照。别让它背锅,也别绕过它。











