db::transaction()闭包方式更可靠,因其自动处理异常回滚与正常提交,避免手动事务中遗漏rollback导致锁表或死锁;且要求表引擎为innodb,否则事务无效。

为什么 Db::transaction() 闭包方式比手动 startTrans()/commit() 更可靠
因为闭包内异常会自动回滚,不用手写 try/catch 和显式 rollback(),避免遗漏或误判。手动方式一旦忘记捕获某处异常(比如模型事件里抛出的异常),事务就卡在开启状态,后续请求可能被锁表。
常见错误现象:SQLSTATE[HY000]: General error: 1205 Deadlock found when trying to get lock 或长时间未提交的事务占用连接。
- 闭包方式会在退出时自动判断:有未捕获异常 →
rollback();无异常 →commit() - 闭包内所有数据库操作共享同一个 PDO 连接和事务上下文,包括
Model::save()、Db::table()->insert()等 - 不支持嵌套闭包事务(外层
Db::transaction()内再调Db::transaction()),会报Transaction already started
Db::transaction() 里调用模型方法要注意什么
ThinkPHP 模型默认使用独立数据库连接实例,直接在闭包里调 UserModel::create() 可能脱离当前事务——除非你确认该模型没改过 $connection 配置,且没启用 use_transaction 以外的连接策略。
使用场景:需要同时更新用户主表 + 关联的 profile 表 + 日志记录,三者必须原子性。
- 推荐统一用
Db::name('user')->insert()或显式指定模型连接:(new UserModel())->connection('default') - 检查模型类是否重写了
getConnection()方法,否则可能走默认连接池,绕过事务 - 若用
Model::saveAll(),确保传入的是数组而非 Collection 对象,后者可能触发多次独立查询
Db::transaction(function () {
Db::name('user')->insert(['name' => 'a']);
// ✅ 安全:复用同一连接
(new OrderModel())->connection('default')->save(['uid' => 123]);
});
事务中执行非数据库操作(如发短信、写文件)的风险
闭包事务只管数据库层面的 commit/rollback,但发短信成功后数据库回滚了,业务就出现状态不一致。这不是框架问题,是架构设计盲区。
典型错误现象:用户注册成功提示已发送验证码,结果因插入地址失败整个事务回滚,但短信已发出。
- 非数据库操作必须放在事务外,或改为“最终一致性”:先落库,再通过队列异步触发
- 如果必须同步做,至少把副作用操作放在事务 之后,并加重试/幂等逻辑
- 日志记录建议用
Log::record()而非file_put_contents(),前者可延迟刷盘,避免事务回滚后日志残留
MySQL 引擎选错导致 Db::transaction() 形同虚设
用 MyISAM 表执行 Db::transaction() 不报错,但实际没有任何事务能力——它根本不支持事务。所有语句都是自动提交的,START TRANSACTION 在 MyISAM 下被忽略。
性能影响:InnoDB 行锁 vs MyISAM 表锁;兼容性影响:高并发下 MyISAM 容易因锁表阻塞。
- 检查表引擎:
SHOW CREATE TABLE user;看输出里是ENGINE=InnoDB还是MyISAM - 迁移命令:
ALTER TABLE user ENGINE=InnoDB;(注意备份) - 新建表时务必显式声明:
CREATE TABLE user (...) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
最容易被忽略的是:项目初期用 SQLite 开发,测试时一切正常,上线 MySQL 后忘了确认引擎,结果生产环境事务失效,问题很难复现。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











