PostgreSQL中GORM需启用WithReturning选项并配合正确字段标签才能通过RETURNING获取完整插入行;MySQL/SQLite不支持该功能,仅能回退为两步查询且无法返回默认值或触发器生成字段。

PostgreSQL 中用 Create + Returning 拿回插入的完整行
GORM 默认在 PostgreSQL 里执行 INSERT 后不返回数据,想直接拿到新记录(比如自增 ID、默认值、触发器生成字段),必须显式启用 RETURNING。这功能只在 PostgreSQL 和 CockroachDB 上原生支持,MySQL / SQLite 不行——别在非 PG 环境里试这个逻辑。
实操上,得用 Session 开启 WithReturning 选项,并确保结构体字段有对应标签:
-
CreatedAt、UpdatedAt这类 GORM 内置字段要加autoCreateTime: true或autoUpdateTime: true标签,否则即使数据库生成了值,GORM 也不会从RETURNING结果里赋值 - 自定义默认值字段(如
status默认'pending')需在 struct tag 中写default:'pending',且数据库列定义也得有DEFAULT - 主键是序列(
SERIAL)或GENERATED ALWAYS AS IDENTITY才能被RETURNING *正确捕获
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"default:'anonymous'"`
CreatedAt time.Time `gorm:"autoCreateTime"`
}
db.Session(&gorm.Session{WithReturning: true}).Create(&user)
// user.ID、user.CreatedAt 现在已被填充
MySQL / SQLite 怎么“模拟” Returning 效果
这两个方言不支持 RETURNING,GORM 会自动 fallback 成两步:先 INSERT,再用 SELECT LAST_INSERT_ID()(MySQL)或 SELECT last_insert_rowid()(SQLite)查 ID,然后 SELECT 全字段。但这里有个坑:它只返回主键值,不会把数据库默认值、触发器计算字段、表达式字段(如 tsvector)带回来。
如果你依赖这些字段,就得手动补查:
- MySQL 下,如果用了
ON UPDATE CURRENT_TIMESTAMP,第二步查出来的UpdatedAt是旧值,不是刚插入时数据库实际设的值 - SQLite 的
last_insert_rowid()在事务中不可靠,多协程并发插入可能串号 - 想拿全部字段,只能自己写
db.First(&user, "id = ?", user.ID),但要注意这会触发额外查询,且无法保证原子性
Returning 字段列表控制与常见报错
默认 WithReturning: true 相当于 RETURNING *,但有时你只想取几个字段(比如只关心新生成的 token 或 uuid),可以传字符串指定:
db.Session(&gorm.Session{
WithReturning: "id, token, created_at",
}).Create(&user)
注意这些错误容易卡住调试:
-
ERROR: column "xxx" does not exist in RETURNING list:字段名大小写不一致,PostgreSQL 默认小写,struct tag 里写了column:XXX却没加双引号,导致 SQL 里写成RETURNING XXX失败 -
panic: reflect: call of reflect.Value.Interface on zero Value:结构体字段没导出(小写开头),GORM 无法写入返回值 - 用
map[string]interface{}接收时,WithReturning不生效——它只对 struct 有效
事务中 Returning 的行为边界
在事务里用 WithReturning 没问题,但要注意:返回的值是数据库当前时刻的快照,不受事务后续修改影响。可这不等于安全——如果事务 rollback,你手里的 user.ID 已经被序列消耗掉,下次插入会跳过这个 ID。
更隐蔽的问题是并发:
- 两个 goroutine 同时插入,都拿到不同 ID,但其中一个事务失败 rollback,另一个 commit 成功——ID 是连续的,但中间那个“已分配未提交”的 ID 就空缺了
- 别拿
RETURNING id做业务唯一标识推断(比如认为“ID 小就插入早”),PG 的序列分配和事务提交顺序不严格一致
真正需要强一致返回值的场景(如发通知、写日志),建议在 COMMIT 成功后再查一次,而不是只信 RETURNING 的瞬间结果。











