GORM事务默认启用预编译,安全依赖于参数化查询;显式PrepareStmt仅优化性能,非安全开关;Raw/Exec拼接SQL会绕过预编译导致注入风险。

GORM 事务本身不直接暴露预编译语句控制权,但它的底层执行天然依赖预编译 —— 只要你不手动拼 SQL,就默认安全。
为什么 GORM 事务里不用显式调用 Prepare
GORM 的所有查询(包括事务内)都通过 database/sql 的 Stmt 接口执行,db.Prepare() 是自动触发的。你调用 tx.Create() 或 tx.Where(...).Find() 时,GORM 已把 SQL 模板和参数分离,交由驱动完成绑定。
- 显式写
tx.Session(&gorm.Session{PrepareStmt: true})并不能“开启预编译”,它只是让 GORM 在首次执行同构查询时缓存*sql.Stmt,后续复用 —— 这是性能优化,不是安全开关 - 若你在事务中混用
tx.Raw("SELECT * FROM users WHERE name = '" + name + "'"),那就立刻绕过预编译,SQL 注入风险回归 -
PrepareStmt: true对单次事务意义不大,更适合高频重复查询场景(如批量导入中的固定 INSERT)
tx.Exec() 和 tx.Raw() 是唯一可能破坏预编译的地方
这两个方法允许你传入原始 SQL 字符串。一旦用了字符串拼接或未校验的变量插值,预编译就被跳过,事务的安全性也跟着失效。
- 错误写法:
tx.Exec("UPDATE users SET balance = balance - " + amountStr)——amountStr若为"100 WHERE id=1; DROP TABLE users;"就完蛋 - 正确写法:
tx.Exec("UPDATE users SET balance = balance - ? WHERE id = ?", amount, userID)—— 问号占位符确保参数被绑定而非拼接 - 更稳妥的做法:优先用 GORM 链式 API(如
tx.Model(&u).Update("balance", u.Balance-amount)),它自动生成参数化 SQL
事务回滚时预编译语句是否还有效?
有效,但仅限于连接未关闭。预编译语句(*sql.Stmt)生命周期绑定在底层 *sql.Conn 上,而 GORM 事务对象 *gorm.DB 封装的是一个带事务上下文的连接。
- 调用
tx.Rollback()后,该连接会被归还到连接池,其上已准备的Stmt通常随连接一并关闭(取决于驱动实现,MySQL 驱动会 close,SQLite 不一定) - 所以不要试图在
Rollback()后继续用同一个tx执行语句 —— 会报错"sql: transaction has already been committed or rolled back" - 也不用担心 Stmt 泄漏:GORM +
database/sql的连接池机制会自动清理闲置连接及其 Stmt
真正容易被忽略的是:事务安全 ≠ 自动防注入。只要你没碰 Raw 或 Exec,GORM 的事务就是预编译的;但一旦破戒,那层防护就只在你写的那一行代码里消失 —— 而且悄无声息。


















