当sql无法用gorm链式方法准确表达(如窗口函数、复杂join、嵌套子查询)时才该用raw;它绕过模型映射与钩子,需手动处理参数安全与结果映射。

什么时候该用 Raw 而不是 Find 或 Where
当你要执行的 SQL 无法用 GORM 的链式方法准确表达时,比如带窗口函数、多表复杂 JOIN、子查询嵌套、或需要精确控制字段别名和 NULL 处理逻辑,Raw 就是合理选择。它不走 GORM 的模型映射流程,绕过预处理和结构体反射开销,但代价是你得自己保证 SQL 安全和结果映射正确。
常见误用场景:用 Raw 查单个用户却手动拼接 ID —— 这完全可以用 First(&user, "id = ?", id) 更安全地替代;或者在能用 Select("name, COUNT(*)").Group("name") 的地方硬写聚合 SQL。
Raw 查询结果怎么映射到结构体或 map
GORM 的 Raw 不自动推断目标类型,必须显式调用 Scan,且目标必须可寻址(即传指针)。映射失败通常不是语法错,而是字段名/类型不匹配。
- 映射到结构体:字段名必须与 SQL 中的列名(或别名)**完全一致**(区分大小写),且类型兼容;
db.Raw("SELECT id, name FROM users").Scan(&users) - 映射到
map[string]interface{}:适合动态列或调试,但注意所有值都是interface{},需手动类型断言;var result map[string]interface{}; db.Raw("SELECT count(*) as total FROM users").Scan(&result) - 跳过映射只取行数:用
Rows()获取*sql.Rows,自行迭代处理;适合流式读取大结果集
参数绑定怎么防 SQL 注入
Raw 支持问号占位符(?)和命名参数(:name),但**不能用字符串拼接变量**。GORM 会把参数值转义后传给底层 driver,拼接字符串等于放弃防护。
- ✅ 正确:
db.Raw("SELECT * FROM users WHERE status = ? AND created_at > ?", "active", time.Now().AddDate(0,0,-7)) - ❌ 危险:
"SELECT * FROM users WHERE id = " + strconv.Itoa(id)——id是用户输入就直接 RCE 风险 - ⚠️ 命名参数需配合
NamedExec或Session:GORM v1.23+ 才原生支持Raw("... :status").Scan(..., sql.Named("status", "active"));旧版建议统一用?
事务里执行 Raw 会不会丢连接上下文
会,如果你从全局 *gorm.DB 实例直接调 Raw,它不感知当前事务。必须用事务对象本身调用:
tx := db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
// ✅ 正确:用 tx.Raw
tx.Raw("UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, fromID).Exec()
// ❌ 错误:用 db.Raw —— 操作在事务外执行,提交时不会回滚
db.Raw("...").Exec()
tx.Commit()
另外,Raw 返回的 *gorm.DB 实例不继承父 DB 的 Hooks 和 Scopes,有自定义回调或软删除需求时得手动重挂。
最常被忽略的是列别名大小写——PostgreSQL 默认小写,MySQL 默认不敏感,但当你用 AS "UserEmail",Go 结构体字段就得叫 UserEmail,而 AS "user_email" 就得对应 UserEmail 或加 gorm:"column:user_email" 标签。没对齐就静默跳过字段赋值,很难排查。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











