PostgreSQL中调用带参数存储过程必须使用db.Exec()配合$1、$2等位置占位符,不可用?或命名参数;CALL语句参数顺序须严格对应,空值传nil,OUT参数需改用Raw().Scan()获取。

PostgreSQL 中用 db.Exec() 调用带参数的存储过程
PostgreSQL 要求 CALL 语句的参数必须用 $1, $2 这类位置占位符,GORM 会自动把 ? 映射为 $1, $2 ——但仅限于 db.Exec(),db.Raw().Scan() 不支持这种映射。
常见错误是写成 "CALL sp_insert_user(?, ?)" 却在 PostgreSQL 驱动下传参失败,实际报错可能是 ERROR: there is no parameter $1 或 sql: expected 0 arguments, got 2。
- 必须用
db.Exec()(不是Raw()),且 DSN 中sslmode等配置要完整 - 参数顺序严格对应
$1,$2…,不能跳过中间参数 - 空值要传
nil,不能传""或0,否则可能触发 NOT NULL 约束失败 - 如果存储过程有 OUT 参数,
Exec()拿不到,得换Raw().Rows()+ 手动读取
MySQL 中调用存储过程时命名参数不生效
MySQL 支持 @var_name 形式命名参数,但 GORM 的 db.Exec() **不解析 SQL 字符串里的 @ 符号**,它只认 ? 占位符。所谓“命名参数”写法其实是误导——GORM 官方文档里那个 sql.Named() 示例只适用于 db.Raw().Scan() 场景,对 CALL 无效。
你写 "CALL sp_login(@user_id, @token)" 并传 sql.Named("user_id", 123),实际执行时 MySQL 收到的是字面量 @user_id,变量根本没被赋值。
- MySQL 下统一用
?占位符,例如"CALL sp_login(?, ?)" - 驱动必须是
github.com/go-sql-driver/mysql,且 import 语句里要有_ "github.com/go-sql-driver/mysql" - 如果存储过程返回结果集(比如 SELECT),必须用
db.Raw().Scan(),Exec()会直接丢弃结果
Raw().Scan() 获取存储过程返回值的正确姿势
当存储过程有 OUT 参数或通过 RETURNING / SELECT 返回数据时,Exec() 完全无能为力,必须切到 Raw() 流程。但这里最容易踩的坑是:以为 Raw() 返回结构体切片,其实它只返回 *gorm.DB,必须链式调用 .Scan() 才真正执行查询并填充目标变量。
典型错误:db.Raw("CALL sp_get_user_by_id(?)", id).Rows() 只拿到 *sql.Rows,没读内容;或者漏写 & 导致 Scan 不生效。
- 返回单行单列(如 COUNT):
var count int; db.Raw("SELECT COUNT(*) FROM users WHERE active = ?", true).Row().Scan(&count) - 返回多行多列:
var users []User; db.Raw("CALL sp_list_active_users()").Scan(&users) - PostgreSQL 的
RETURNING必须用Raw().Scan(),Exec()不返回结果集
事务内调用存储过程要注意隔离级别
GORM 的事务对象(tx := db.Begin())传给 Exec() 或 Raw() 是安全的,但有个隐藏细节:存储过程内部如果显式执行 BEGIN / COMMIT,会和外层 GORM 事务冲突,导致报错 ERROR: cannot insert multiple commands into a prepared statement 或静默提交部分数据。
尤其在 PostgreSQL 中,带 COMMIT 的存储过程无法嵌套进 GORM 事务,MySQL 的 START TRANSACTION 同理。
- 存储过程应设计为“无事务控制”,把事务管理权完全交给 Go 层
- 如果必须用内部事务,改用
db.Session(&gorm.Session{PrepareStmt: false})绕过预编译,但会损失性能 - 调试时用
pg_stat_activity或information_schema.innodb_trx查看真实事务状态,别只信 GORM 日志
实际用的时候,最麻烦的不是语法,而是存储过程本身是否“配合事务”、是否“真返回数据”、以及 PostgreSQL 和 MySQL 对 CALL 语义的细微差别——这些不会报错,但会让结果和预期差很远。











