空指针引发的事务中断源于未校验参数或字段非空,GORM不自动拦截nil写入或兜底panic;需在事务回调开头强制判空、避免复用外部指针、提前校验联动数据。

空指针引发的事务中断不是GORM本身的问题,而是你没在事务入口处做参数校验或结构体字段判空——它不会自动帮你拦住 nil 指针写入,也不会替你兜底 panic 后的事务悬挂。
事务回调里传了 nil 结构体,db.Transaction 不报错但后续操作全挂
这是最隐蔽的坑:GORM 的 Transaction 函数只负责启停事务,不校验你传进去的回调函数里用了什么数据。如果你在回调里直接用了一个未初始化的 *User 变量(比如 var u *User 没 new),后面调 tx.Create(u) 会 panic,而 GORM 1.20 虽然能捕获 panic 并转为 error,但前提是 panic 发生在事务回调内部;如果 panic 在回调外、或发生在 defer 里,事务就可能卡住。
实操建议:
- 所有进事务的数据对象,必须在
Transaction回调开头做非空检查:if u == nil { return errors.New("user cannot be nil") } - 避免在事务回调中复用外部作用域的指针变量,优先用新声明 + 显式赋值的方式构造数据
- 若涉及多个实体联动(如创建订单同时扣库存),把校验逻辑提前到事务外,失败直接返回,别让无效数据进事务上下文
tx.Create 或 tx.Save 遇到零值字段,数据库写入失败却没报错
典型现象是 MySQL 报 Column 'name' cannot be null,但 Go 层 err 为空。原因在于:GORM 默认把 struct 字段零值(如 string 的 ""、int 的 0)当作“要写入的值”,而不是“跳过”。如果数据库列定义为 NOT NULL 且无默认值,就会被拒,但 GORM 不一定把底层 driver 的错误透出到上层 Error 字段。
实操建议:
- 模型字段该用指针类型就用指针类型(如
*string),这样nil才能明确表达“不更新”或“设为 NULL”语义 - 慎用
Save():它是全量覆盖,哪怕字段是""也会写入,容易误覆非空约束字段;改用Updates(map[string]interface{})或Select().Updates()显式控制字段集 - 建表时对业务上允许缺失的字段显式加
NULL约束,别依赖 ORM 的“自动推断”
关联查询返回 nil 切片,JSON 序列化成 null 导致前端崩溃
比如 User.Posts 是 []Post 类型,查不到数据时 GORM 设为 nil 而非 []Post{},json.Marshal 输出 "posts": null,前端 JS 调 data.posts.map 直接报错。
这不是事务问题,但它常和事务内查询混在一起,且错误堆栈容易误导你去查事务逻辑。
实操建议:
- 不要靠
Preload后手动初始化切片(易漏、难维护),统一走json.Marshaler接口重写 - 在父结构体里实现
MarshalJSON,把所有关联切片字段的nil替换为空切片,例如:if f.Posts == nil { f.Posts = []Post{} } - 如果用的是嵌套结构体(如
type User struct { Profile *UserProfile }),同样要对指针字段判空并提供默认值,否则 JSON 里还是"profile": null
真正危险的不是空指针本身,而是它藏在事务链路中间——你以为 rollback 了,其实部分 SQL 已执行;你以为 error 被 catch 了,其实 panic 发生在 defer 里,事务没关干净。每次写事务回调,第一行先写校验,比加日志还重要。


















