thinkphp 8.0 事务是否生效取决于引擎(必须innodb)、连接配置、手动调用commit/rollback、跨库不支持原子性、嵌套事务无效且模型切换连接会破坏事务一致性。

ThinkPHP 8.0 的事务开启本身很简单,但**是否真正生效、是否覆盖全部操作、是否在多库或多连接场景下失效**,才是实际开发中最常翻车的地方。别急着写 Db::startTrans(),先看清楚你踩在哪一环。
事务不生效?先确认数据库引擎和连接配置
MySQL 的 MyISAM 引擎根本不支持事务,哪怕你代码里写了 startTrans(),也只会静默忽略——没有报错,但 rollback() 完全没用。必须确保:
- 目标表使用
InnoDB引擎(可通过SHOW CREATE TABLE think_user验证) - 数据库连接配置中未显式关闭事务支持(ThinkPHP 默认开启,无需额外开关)
- 没在模型或查询中误用了
->useTransaction(false)这类禁用标记(极少见,但自定义封装里可能埋雷)
手动事务:try/catch 里漏了 commit 或 rollback 就等于没开
这是最典型的“以为开了事务,其实没提交也没回滚”的情况。下面这段代码看着规范,实则危险:
Db::startTrans();
try {
Db::table('order')->insert($orderData);
Db::table('log')->insert($logData);
// 忘了 Db::commit();
} catch (\Exception $e) {
Db::rollback();
}
后果是:连接一直挂着事务状态,后续同连接的查询可能被锁住,或因超时自动回滚但无日志提示。务必保证:
-
commit()和rollback()都要显式调用,不能只写一个 - 不要依赖 finally ——
finally里不能判断该 commit 还是 rollback,得靠 try 内部逻辑控制 - 若用
Db::transaction(function () { ... })自动模式,函数内抛出异常才会触发回滚;return 任何值都不影响提交
跨库操作时 startTrans() 是假动作,别信它
当你调用 Db::connect('order_db') 和 Db::connect('user_db') 分别操作两个物理数据库时,startTrans() 对它们各自独立生效。A 库提交成功、B 库失败,程序仍可能返回“成功”,数据已不一致。
原因很直接:事务绑定在 PDO 实例上,两次 connect() 创建的是两个完全隔离的连接。此时:
-
Db::startTrans()只作用于当前连接,切换连接后需重新调用 - 不存在“跨库原子性”,ThinkPHP 无 XA 事务封装,MySQL 8.0+ 原生 XA 在生产环境基本不用(死锁率高、运维难)
- 真实可行的方案只有应用层补偿:先写主库 + 记日志,再异步写辅库,失败走重试 + 幂等校验
事务嵌套和模型切换容易导致意外提交
ThinkPHP 不支持真正的嵌套事务(savepoint),多次 startTrans() 只会覆盖前一次状态。更隐蔽的问题是:你在事务中调用了一个模型方法,而该模型内部又调用了 Db::connect() 切换连接并执行了 startTrans() —— 这个子事务和你的外层事务毫无关系。
典型陷阱场景:
- 订单服务里用默认连接开启事务,同时调用用户服务的
UserModel::updateBalance() - 而
UserModel内部指定了protected $connection = 'user_db',它另起一个连接、另起一个事务 - 结果:订单写入回滚了,余额却已扣减,且无法通过外层
rollback()拉回来
解决办法不是加更多 try/catch,而是统一连接上下文,或把跨域操作移出事务边界,改用最终一致性设计。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











