tx.commit() 必须显式调用并检查返回值,否则事务静默丢弃;它不自动提交,可能因网络、死锁等失败,忽略错误会导致上层误判成功,且gorm中同样需手动提交。

tx.Commit() 必须显式调用,且必须检查返回值;漏掉这行,或忽略它的 error,事务就静默丢弃——数据没写进去,也不报错,查库发现“什么都没发生”。
为什么 tx.Commit() 一定要写、还要检查 error
Go 的 database/sql 和 GORM 都不自动提交。你调 db.Begin() 或 db.Transaction() 只是开了个连接上下文,函数跑完、甚至 panic 捕获完,只要没调 tx.Commit(),连接就归还池子,事务直接丢弃。
-
tx.Commit()可能失败:网络断开、死锁、唯一键冲突、数据库连接已关闭 - 只写
tx.Commit()不接返回值 → 上层完全感知不到失败,以为成功了 - 写成
err = tx.Commit()却忘了在函数末尾return err→ 错误被吞掉,调用方认为操作成功 - GORM 中同理:
tx.Create()成功不代表事务提交,仍要tx.Commit()
tx.Commit() 的正确写法(含 error 处理)
不是“调一下就行”,而是要让它真正参与错误流。以下两种写法等价且推荐:
if err := tx.Commit(); err != nil { return err }err := tx.Commit(); if err != nil { return err }
别写这些:
-
tx.Commit()(无接收,error 被丢弃) -
err = tx.Commit(); return nil(覆盖了 err,但没返回) -
defer tx.Commit()(defer 在函数退出时执行,但此时可能已 panic 或提前 return,commit 时机错乱)
嵌套调用里 tx.Commit() 容易踩的坑
GORM 的 db.Transaction() 套 db.Transaction() 不是真嵌套事务,而是 SAVEPOINT。这意味着:
- 内层
tx.Commit()实际是RELEASE SAVEPOINT,不会真正提交 - 外层
tx.Commit()才是最终提交;如果外层没 commit,所有 savepoint 都白做 - 内层手动调
tx.Rollback()会触发ROLLBACK TO SAVEPOINT,但名字不对或 MySQL 5.7 用单字母名,直接报ERROR: no such savepoint - 别在内层事务闭包里写
return tx.Commit()—— 这只会释放 savepoint,不是终局提交
HTTP / I/O 操作不能等 tx.Commit() 结果
事务里发 HTTP 请求、写文件、调 RPC,一旦超时或失败,会拖住整个事务,锁不释放,连接池迅速耗尽。而 tx.Commit() 成功后,才能安全触发这些副作用。
- GORM 用
tx.AfterCommit(func() {}),但注意:钩子只对原始tx实例生效,tx.Session()或tx.WithContext()新建的实例不继承它 - 原生
*sql.Tx没有 AfterCommit,得靠 channel 或异步任务投递:go func() { http.Post(...) }()放在tx.Commit()成功之后 - 别把
AfterCommit当事务一部分 —— 它不参与回滚,失败了也不会 rollback 已提交的数据
tx.Commit() 这行代码必须出现在成功路径的最后、必须绑定 error 返回、必须和所有 db 操作共用同一个 tx 实例——少一个条件,数据就悬在半空。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











