Raw() 不执行 SQL,必须链式调用 Scan()、Rows() 或 Row() 才真正发请求;它仅返回未执行的 *gorm.DB 构建器,参数须用 ? 占位且扁平传入,事务中需用 tx.Raw() 而非 db.Raw()。

Raw() 不执行 SQL?得链式调用 Scan() 或 Rows() 才真正发请求
很多人写 db.Raw("SELECT * FROM users") 就以为 SQL 已执行,结果发现变量没变、也没报错——因为 Raw() 只返回一个未触发的查询构建器(*gorm.DB),它本身不连数据库。
必须显式链式调用才能执行:
-
.Scan(&u):映射单行或切片,适用于结构体或基础类型 -
.Rows():返回*sql.Rows,适合手动遍历、动态列处理 -
.Row():只取第一行,配合.Scan()读单值(如COUNT(*))
错误写法:db.Raw("SELECT name FROM users").Scan(&name) 如果 name 是 string 类型而非指针,会 panic;正确是 var name string; db.Raw(...).Row().Scan(&name)。
Scan() 映射失败?字段名、导出性、大小写全得对上
Scan() 不走 GORM 的蛇形转驼峰逻辑,它严格按 SELECT 列名(区分大小写)匹配结构体字段名。常见失败点:
- 结构体字段必须首字母大写(导出),小写字段(如
name string)直接被忽略 - SELECT 中用了别名(如
SELECT user_name AS UserName),结构体就得有UserName string字段,或加gorm:"column:UserName"标签 - MySQL 默认返回小写列名,PostgreSQL 默认返回小写(除非双引号包裹),所以
SELECT id, name对应ID int和Name string,不是Id/name - 查了 3 列但结构体只有 2 个字段?GORM 不报错,缺失字段留零值——容易漏数据
示例:要扫 SELECT created_at AS CreatedAt, status FROM orders,结构体必须含 CreatedAt time.Time 和 Status string,且都导出。
参数绑定写成 $1 或 :name?GORM Raw() 默认只认 ?
MySQL、SQLite、PostgreSQL 驱动在 GORM Raw() 中统一用 ? 占位符。写成 、:id 或字符串拼接,轻则参数不生效,重则报错 sql: expected 0 arguments, got 1 或语法错误。
正确姿势:
- 所有数据库都用
?:db.Raw("SELECT * FROM users WHERE id > ? AND name LIKE ?", 100, "%li%") - 传参必须扁平展开,不能塞进切片再解包:
args := []interface{}{100, "%li%"}; db.Raw(sql, args...)✅,但db.Raw(sql, args)❌(GORM 会把整个切片当第一个参数) - nil 值要显式处理:传
sql.NullString,或改用map[string]interface{}+NamedExec()(需额外配置)
事务里 Raw() 没生效?默认脱离事务上下文
直接用 db.Raw() 在事务中执行,哪怕你在 tx := db.Begin() 之后调,SQL 也大概率跑在新连接上,Commit 后看不到效果。
根本原因是:Raw() 底层调的是 *sql.DB.Query(),而事务对象 tx 是 *sql.Tx ——两者不同源。
必须显式用事务实例:
- ✅ 正确:
tx.Raw("UPDATE accounts SET balance = ? WHERE id = ?", newBal, id).Scan(&updated) - ❌ 错误:
db.Raw(...).Scan(...)即使db是事务开始的句柄,也不保证在事务内 - UPDATE/INSERT 带
RETURNING(PostgreSQL)必须用tx.Raw().Scan(),否则拿不到返回值
容易被忽略的一点:事务中混合 ORM 操作(如 tx.Create())和 tx.Raw() 时,所有操作必须共用同一个 tx 实例,不能混用 db 和 tx。


















