GORM命名参数仅支持map[string]interface{}或sql.NamedArg,不支持:name/@id等数据库原生风格;Raw和Exec可识别@key,链式方法(如Where)只支持?位置参数。

命名参数必须用 map[string]interface{} 或 sql.NamedArg
GORM 不支持类似 :name 或 @id 这种数据库原生风格的命名占位符;它只认 Go 层传入的 map[string]interface{} 或 sql.NamedArg。直接写 WHERE name = :name 会报错或静默忽略参数。
常见错误现象:Raw("SELECT * FROM users WHERE name = :name").Scan(&u) 返回空结果,但日志里 SQL 显示 WHERE name = ?,参数根本没代入。
- 正确写法是
_db.Raw("SELECT * FROM users WHERE name = @name", map[string]interface{}{"name": "alice"}) - 或者用
sql.NamedArg:_db.Raw("SELECT * FROM users WHERE name = @name", sql.Named("name", "alice")) - 注意:键名(如
"name")必须和 SQL 中的@name完全一致,大小写敏感 - 不支持混合使用,比如
@name和?同时出现在一条语句里
Exec 也支持命名参数,但行为和 Raw 略有不同
Exec 虽然也接受 map[string]interface{},但它对参数绑定更宽松——即使 SQL 里写的是 ?,GORM 也会尝试从 map 中按顺序取值(不推荐依赖此行为)。真正安全的做法仍是统一用 @key 风格 + map。
- 推荐写法:
_db.Exec("UPDATE users SET age = @age WHERE id = @id", map[string]interface{}{"age": 25, "id": 123}) - 错误写法:
_db.Exec("UPDATE users SET age = ? WHERE id = ?", map[string]interface{}{"age": 25, "id": 123})—— 这里?会触发位置参数逻辑,map 被忽略,实际传的是空值 - 如果用了
sql.NamedArg,Exec和Raw行为一致,可放心复用
命名参数在 Where 链式调用中不生效
很多人误以为 db.Where("name = @name", map[string]interface{}{"name": "bob"}) 能工作,其实不能。GORM 的链式方法(Where、Order、Limit)只解析位置参数(?),不识别 @key。
- 下面这行代码中的
@name是纯字符串,不会被替换:db.Where("name = @name", params).Find(&u) - 想用命名参数又保持链式风格?只能先拼好完整条件再进
Where:db.Where("name = ?", params["name"]).Find(&u) - 或者放弃链式,改用
Raw:db.Raw("SELECT * FROM users WHERE name = @name", params).Scan(&u)
性能与兼容性:MySQL/PostgreSQL 可靠,SQLite 有坑
命名参数底层依赖驱动对 sql.NamedArg 的支持程度。MySQL 和 PostgreSQL 驱动处理得很稳;SQLite 驱动(gorm.io/driver/sqlite)在较老版本中会丢弃命名参数,降级为位置参数,导致顺序错乱。
- 检查 SQLite 行为最简单的方式:开 Debug 日志,看生成的 SQL 是否真把
@name替换成了实际值 - 若发现参数未代入,优先升级
gorm.io/driver/sqlite到 v1.5.0+ - 生产环境涉及多数据库时,建议统一用
map[string]interface{}+@key,避免混用?和命名参数带来的可维护性问题
实际用的时候,最容易忽略的是链式 API 和原生 SQL 对命名参数的支持边界——Raw 和 Exec 是唯二真正吃透 @key 的入口,其余所有链式方法都只是“假装支持”。


















