db.raw()执行原生sql必须用?占位符防注入,scan()要求字段名、顺序、类型严格匹配结构体;exec()专用于无结果集操作,返回影响行数;事务中须全程使用tx实例。

用 db.Raw() 执行带参数的原生查询
直接拼接 SQL 字符串是危险的,GORM 提供 db.Raw() 配合 Scan() 或 Find() 安全传参。它底层使用数据库驱动的预处理机制,自动转义参数,避免 SQL 注入。
常见错误是把变量直接插进字符串里,比如 "SELECT * FROM users WHERE id = " + strconv.Itoa(id) —— 这在任意用户可控输入下都可能被绕过。
-
db.Raw("SELECT name, email FROM users WHERE age > ?", 18).Scan(&users):适合结果需映射到结构体,&users是切片指针 -
db.Raw("SELECT COUNT(*) FROM users WHERE status = ?", "active").Row().Scan(&count):查单值用Row().Scan() - 多个参数按顺序传入,不支持命名参数(如
:name),除非你用sql.Named手动包装(但 GORM 不默认识别)
用 db.Exec() 执行 INSERT/UPDATE/DELETE 等无返回结果语句
db.Exec() 用于执行不返回行数据的语句,比如写操作或 DDL。它返回 *sql.Result,可调用 RowsAffected() 检查影响行数,但不会解析字段或填充结构体。
注意它和 Raw().Scan() 的分工:前者只管执行,后者才负责取结果。混用会导致 panic 或空结果。
-
db.Exec("UPDATE users SET email = ? WHERE id = ?", newEmail, userID):参数顺序必须严格匹配占位符 -
result, _ := db.Exec("INSERT INTO logs (msg) VALUES (?)", msg); affected, _ := result.RowsAffected():获取影响行数 - 不建议用
Exec()做 SELECT;即使语法合法,也无法通过它拿到结果集
原生 SQL 和 GORM 模型字段名不一致时怎么映射
写原生 SQL 时列名常是蛇形(created_at),而 Go 结构体字段是驼峰(CreatedAt)。GORM 默认不会自动转换,需要显式指定别名或用 map[string]interface{} 中转。
最稳的方式是 SQL 里用 AS 显式别名,让列名和结构体字段名完全一致:
- 正确:
db.Raw("SELECT id, name AS Name, email AS Email FROM users").Find(&users) - 错误:
db.Raw("SELECT id, name, email FROM users").Find(&users)(字段名对不上,name不会赋给Name) - 若不想改 SQL,可用
map[string]interface{}先读出 map,再手动赋值——适合字段少、逻辑临时的场景
事务中混合使用原生 SQL 和 GORM 方法的风险
在事务里调用 db.Raw() 或 db.Exec() 是安全的,前提是全程用同一个 *gorm.DB 实例(即从 tx := db.Begin() 开始的 tx),否则会脱离事务上下文,变成自动提交。
典型坑是:写了 tx.Raw(...).Scan(),但后续又用了原始 db.Exec()(没用 tx),导致部分操作不在事务里。
- 务必确认所有操作都基于事务对象:
tx.Raw(...)、tx.Create(...)、tx.Commit() - 原生 SQL 无法触发 GORM 的钩子(
BeforeCreate等),时间戳、软删除字段需手动处理 - 如果 SQL 中涉及关联表或复杂条件,且后续还要用 GORM 的
Preload,建议放弃原生,改用 GORM 链式查询——混合使用会让逻辑割裂、难维护
原生 SQL 最容易被忽略的点,其实是字段名映射和事务对象一致性;这两处出错,现象往往是“查不到数据”或“更新没生效”,但日志里看不出异常。











