Raw() 返回的 *gorm.DB 需链式调用 Scan()/Find()/Rows() 才执行;参数用? 占位;Scan 目标须导出且类型匹配;不触发钩子、软删除、Preload 等 GORM 特性。

Raw() 返回的 *gorm.DB 不能直接用 Scan()
很多人写 db.Raw("SELECT name FROM users").Scan(&name) 报错,因为 Raw() 返回的是一个未执行的查询构建器,不是结果集。它得先调用 Scan()、Find() 或 Rows() 才真正发请求。
-
Raw()只拼 SQL,不执行;必须链式调用Scan()(结构体 / 变量)或Rows()(获取*sql.Rows)才能触发执行 - 如果只想要单个值(比如 count),用
Count()更安全,避免 Scan 类型不匹配 - 用
Scan()时,目标变量类型必须和 SELECT 字段严格对应:SELECT 两个字段就得扫进有两个字段的 struct 或两个变量的 slice
参数绑定要用 ? 而不是 $1 或 :name
GORM 的 Raw() 默认使用数据库驱动原生占位符,MySQL/SQLite 用 ?,PostgreSQL 默认也走 ?(除非显式启用 WithClause(clause.SQL{SQL: "……"}) 或改配置)。写成 $1 或 :name 在大多数情况下直接报 sql: expected 0 arguments, got 1 或语法错误。
- 正确写法:
db.Raw("SELECT * FROM users WHERE id > ?", minID).Find(&users) - 多个参数按顺序填入:
db.Raw("SELECT * FROM orders WHERE status = ? AND created_at > ?", "paid", time.Now().AddDate(0,0,-7)) - 想用命名参数?GORM 不原生支持;可手动拼接(不推荐,注意 SQL 注入),或改用
Session(&gorm.Session{PrepareStmt: true})+ 驱动层支持(如 pgx 对 PostgreSQL 的$1支持需额外配置)
Scan 到 struct 时字段名必须匹配,且 struct 要有公开字段
常见错误是 struct 字段没加导出(首字母大写),或者 json:/gorm: tag 写错导致映射失败,结果扫出来全是零值,还不报错。
- struct 字段必须是公开的(首字母大写),否则 GORM 无法反射赋值
- 字段名默认按蛇形转驼峰匹配(
user_name→UserName),但若字段名含大小写混合(如APIKey),对应数据库列应为api_key;不一致就加gorm:"column:api_key" - 如果 SELECT 的列名和 struct 字段完全不相关(比如用了 AS 别名),必须确保别名符合驼峰规则,或改用
map[string]interface{}接收再手动转
Raw 查询不走 GORM 钩子和关联预加载
这是最容易被忽略的点:你写了 BeforeCreate 钩子、启用了软删除、甚至配了 Preload("Profile"),但 Raw() 一上,全失效。它绕过 GORM 的整个模型层,直连 *sql.DB。
立即学习 “go 语言免费学习笔记(深入)”;
- 软删除字段(
deleted_at)不会自动加到 WHERE 条件里 -
TableName()方法、gorm.Model(&u)指定的表名也不会生效,得自己写进 SQL 字符串 - 事务内执行没问题,但关联数据得自己 JOIN 或额外查,
Preload和Joins对Raw()无效 - 如果只是想加点条件或排序,优先考虑用 GORM 链式方法(
Where()、Order()),而不是急着切 Raw
GORM 的 Raw() 是逃生舱,不是驾驶舱。它快、灵活,但也意味着你得自己扛字段映射、参数安全、生命周期管理——尤其当项目开始加审计字段、租户隔离、动态 schema 时,裸 SQL 很容易漏掉一层过滤。