db.Raw()是GORM执行原生SQL的安全入口,必须用?占位符或命名参数防注入,禁用字符串拼接;Scan()用于结构化结果映射,Rows()用于动态字段处理;事务中须用tx.Raw()确保一致性。

用 db.Raw() 执行带参数的原生 SQL
直接拼接字符串执行 SQL 极易引发 SQL 注入,db.Raw() 是 GORM 提供的安全入口。它支持占位符(?)和命名参数(:name),底层会交由数据库驱动做预处理。
常见错误是把变量直接插进 SQL 字符串里,比如 "SELECT * FROM users WHERE id = " + strconv.Itoa(id) —— 这完全绕过了 GORM 的参数绑定机制,等同裸奔。
- MySQL/PostgreSQL 推荐用
?占位符:db.Raw("SELECT * FROM users WHERE age > ? AND status = ?", 18, "active").Scan(&users) - SQLite 和部分场景下可用命名参数:
db.Raw("SELECT * FROM users WHERE name = :name", map[string]interface{}{"name": "alice"}) - 注意:命名参数在 MySQL 驱动中默认不支持,需启用
parseTime=true&loc=Local并确认驱动版本 ≥ v1.4.0
Scan() 和 Rows() 的适用场景区别
Scan() 适合结果结构已知、能映射到 Go 结构体或基础类型切片的场景;Rows() 则用于字段动态、列数不确定或需逐行处理的原生查询。
典型误用是强行用 Scan(&struct{}) 去接 SELECT COUNT(*) 或多表 JOIN 后字段名冲突的结果 —— GORM 无法自动匹配,会静默忽略字段或报 sql: expected 1 destination argument 错误。
- 查单个值(如 count):
var count int64; db.Raw("SELECT COUNT(*) FROM posts").Row().Scan(&count) - 查多行多列且结构固定:
db.Raw("SELECT id, title FROM posts WHERE published = ?", true).Scan(&posts) - 查未知结构或需类型推导:
rows, _ := db.Raw("SELECT * FROM logs LIMIT 10").Rows(); defer rows.Close(),然后用rows.Columns()和rows.Scan()手动取值
事务中执行原生 SQL 必须复用同一个 *gorm.DB 实例
在事务内调用 db.Raw() 时,如果误用了全局 db 或新初始化的实例,SQL 将脱离事务上下文,导致部分成功、数据不一致。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
例如,在 db.Transaction(func(tx *gorm.DB) error { ... }) 内部写了 globalDB.Raw(...).Exec(),这条语句根本不会被回滚。
- 正确做法:所有操作都基于传入的
tx实例,tx.Raw("UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, id).Exec() - 注意
Exec()不返回结果集,只返回影响行数和错误;若需查数据,仍用Scan()或Rows() - PostgreSQL 中执行
INSERT ... RETURNING时,必须用Scan(),不能用Exec()
DDL 操作和特殊语法的兼容性陷阱
GORM 对 CREATE TABLE、ALTER COLUMN 等 DDL 语句不做任何封装或校验,能否执行完全取决于底层数据库驱动是否支持该语法,且多数驱动不支持在单条语句中执行多个 DDL(如用分号隔开)。
常见失败场景包括:MySQL 8.0+ 的 JSON_CONTAINS 在低版本驱动中报 function not found;SQLite 的 ALTER TABLE ... RENAME COLUMN 在驱动未启用 _cgo 时静默失败。
- 跨库迁移时,避免写
db.Raw("CREATE TABLE IF NOT EXISTS ..."),改用 GORM 的Migrator().CreateTable()更可靠 - 执行
EXPLAIN类调试语句,务必用Rows()获取原始输出,Scan()会尝试结构化解析而报错 - PostgreSQL 中使用
$$包裹函数体(如CREATE OR REPLACE FUNCTION),需确保字符串中反斜杠未被 Go 字符串字面量提前转义
GORM 的原生 SQL 支持足够轻量,但它的“透明”恰恰是最容易出问题的地方——看起来像普通方法调用,实则跳过了 ORM 层的所有安全检查和抽象逻辑。真正难的不是写对那行 db.Raw(),而是判断此刻到底该不该跳出 ORM。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










