gorm 中 db.raw() 仅构建原始 sql 但不执行,执行更新等写操作必须调用 db.exec() 并检查 sql.result 获取影响行数或错误。

gorm.Raw() 不是编译器,但它是绕过 GORM 默认 SQL 生成的最直接方式
很多人误以为 GORM 提供了可插拔的「SQL 编译器」机制,实际上它没有。GORM 的查询构建逻辑(比如 Where、Joins、Preload)最终会调用各驱动实现的 dialector 接口来生成 SQL,而这个过程对用户是封闭的、不可替换的。你无法注册一个自定义的「MySQL 编译器」或「PostgreSQL 编译器」来接管整个 DSL 到 SQL 的转换。
真正能控制 SQL 输出的入口只有两个:Raw() 和 Exec()。它们跳过所有 GORM 中间层,把字符串原样交给数据库驱动执行 —— 这就是你「针对特定数据库」写 SQL 的事实标准路径。
- MySQL 特有语法(如
INSERT ... ON DUPLICATE KEY UPDATE)必须用db.Raw()或db.Exec() - PostgreSQL 特有语法(如
RETURNING *、JSONB_CONTAINS)同理,不能指望First()或Updates()自动生成 - SQLite 的
REPLACE INTO或WITHOUT ROWID表声明也必须手写Exec() - 别试图用
Session(&gorm.Session{DryRun: true})拦截并重写生成的 SQL —— 它只输出,不提供修改钩子
想让 Raw SQL 与 GORM 模型/事务/日志体系共存,得注意三件事
Raw() 虽然自由,但不是孤立操作。它仍走 GORM 的连接获取、事务绑定、日志记录和错误包装流程,前提是调用方式正确。
- 必须用
db.Raw("...").Scan(&v)或db.Raw("...").Find(ctx)(GORM v2.1+ 推荐),而不是自己拿sql.DB—— 否则丢失事务上下文 - 参数一律用
?占位符(MySQL/SQLite)或$1, $2(PostgreSQL),不要拼接字符串;GORM 会根据当前dialector自动适配占位符风格 - 如果用了命名参数(如
sql.Named("name", "tom")),需确认驱动支持:MySQL 驱动不原生支持命名参数,会 fallback 到位置参数,PostgreSQL 驱动支持 - 日志里看到的 SQL 是最终发送给数据库的形态,包括占位符替换后的结果(取决于
LogLevel设置为Info)
多数据库兼容的 Raw SQL 写法,没有银弹,只有约束性约定
如果你的应用要同时支持 MySQL 和 PostgreSQL,又必须用 Raw SQL,那就得主动收敛语法差异,而不是指望 GORM 帮你翻译。
- 避免使用
IFNULL()(MySQL)或COALESCE()(通用但 PostgreSQL 更常用)—— 统一用COALESCE(),它在两者中行为一致 - 分页统一用
LIMIT ? OFFSET ?,不要用 PostgreSQL 的OFFSET ? LIMIT ?(顺序不同)或 MySQL 的LIMIT ?, ?(双参数形式) - 时间函数尽量用 ANSI 标准:用
NOW()而非SYSDATE()(MySQL)或CURRENT_TIMESTAMP(PostgreSQL)—— 实测NOW()在两者中都可用 - 建表语句这类 DDL 必须按环境拆开:用配置项判断当前
dialector.Name(),再加载对应 SQL 文件,不要混写
真需要「编译器级」定制?只能 fork dialector 或用 Gen 的 query 包封装
极少数场景下,你确实需要改变 GORM DSL 生成 SQL 的底层规则(比如强制所有 LIKE 查询自动加 LOWER() 包裹,或给所有字符串字段加 COLLATE utf8mb4_0900_as_cs)。这时有两个现实选择:
- 直接修改或继承官方
dialector:例如复制gorm.io/driver/mysql下的mysql.go,重写Explain、BindVar或BuildCondition方法 —— 成本高,升级困难,仅限深度定制项目 - 用
gen工具生成的query包替代原生 GORM 调用:它把每个模型的 CRUD 封装成可读性强、可修改的 Go 函数,在函数内部用Raw()或组合Where(),相当于人工写了「轻量编译器」 - 永远别碰
gorm.Model().Clauses()试图注入方言特性 ——Clauses是为通用扩展设计的(如Locking),不是 SQL 重写接口
真正难的从来不是写出一条能跑的 Raw SQL,而是让这条 SQL 在事务里可靠、在日志里可查、在多个数据库上语义一致、在代码里不散落成魔法字符串 —— 这些细节比「编译器」名字重要得多。











