thinkphp6事务回滚不生效的三大主因:表引擎非innodb、异常被try-catch吞没未抛出、操作跨不同数据库连接;需逐一排查并修复。

ThinkPHP6事务回滚不生效,90%以上情况不是框架坏了,而是三个硬性条件缺一:表引擎不是 InnoDB、异常被 try-catch 吞掉没穿透、操作跨了不同数据库连接。
检查 MySQL 表引擎是不是 InnoDB
MyISAM 引擎压根不支持事务,Db::transaction() 或 Db::startTrans() 会静默失效——不报错、不警告、数据照写。这是最隐蔽的“假事务”。
- 执行
SHOW CREATE TABLE think_user;,看输出末尾是否为ENGINE=InnoDB - 如果是
ENGINE=MyISAM,立刻执行:ALTER TABLE think_user ENGINE=InnoDB ROW_FORMAT=DYNAMIC; - 低版本 MySQL(如 5.6)建表时未显式指定
ROW_FORMAT,可能触发 TP6 的兼容问题,DYNAMIC是必须项 - 批量修复所有表?用
SELECT CONCAT('ALTER TABLE ', table_name, ' ENGINE=InnoDB ROW_FORMAT=DYNAMIC;') FROM information_schema.tables WHERE table_schema = 'your_db' AND engine = 'MyISAM';生成语句再执行
Db::transaction() 里写了 try-catch 却没 throw
框架只对「未被捕获的异常」自动回滚。一旦你在闭包里 catch(\Exception $e) 后只记日志、不 throw $e,框架就认为一切正常,直接 commit()。
- ❌ 错误写法:
Db::transaction(function() { try { Db::name('order')->insert([]); throw new \Exception('fail'); } catch (\Exception $e) { Log::error($e->getMessage()); } });→ 数据已提交 - ✅ 正确写法(推荐):
Db::transaction(function() { Db::name('order')->insert([]); Db::name('log')->insert(['msg' => 'created']); });→ 异常自然穿透,自动回滚 - 需要自定义处理?改用手动模式:
Db::startTrans(); try { ... } catch (\Throwable $e) { Db::rollback(); throw $e; }—— 注意用\Throwable,兼容 PHP 8.0+ 的\Error
事务内混用 db('table') 或 Db::connect()
事务绑定在 PDO 连接实例上。db('user') 或 Db::connect('slave') 会创建新连接,导致 SQL 执行脱离事务上下文——rollback() 成了无的放矢。
- 现象:
Db::rollback()不报错,但查库发现数据已写入;日志里看不到BEGIN,或报There is no active transaction - 验证是否同连接:
var_dump(Db::getPdo());在事务前后各执行一次,对象 ID 必须一致 - 读写分离场景下查刚插入的数据?强制走主库:
Db::master()->name('user')->select() - 模型方法想参与事务?别调
$model->connection = 'slave',统一用默认连接;或显式传入:new UserModel(['connection' => Db::getConnect()])
嵌套 Db::transaction() 或混用 startTrans()
TP6 不支持真正嵌套事务。多次调用 Db::transaction() 只是递增计数器,底层仍为单层事务。内层异常不会传导至外层,极易造成部分写入。
- ❌ 危险写法:
Db::transaction(fn() => userCreate()); Db::transaction(fn() => orderCreate());→ 外层不管内层成败,照常 commit - ✅ 唯一可靠做法:所有关联操作收进一个闭包:
Db::transaction(function() { userCreate(); orderCreate(); stockDeduct(); }); - 需要局部回滚(比如发券失败不影响扣款)?只能靠
SAVEPOINT:Db::execute('SAVEPOINT sp_coupon');+Db::execute('ROLLBACK TO SAVEPOINT sp_coupon'); - 注意:
SAVEPOINT名称要合法且唯一,MySQL 5.7+ 支持,8.0+ 更稳定;低版本只能业务降级,靠代码判断跳过后续分支
最易被忽略的是:事务回滚后 PDO 连接状态并未重置。若发生死锁或超时,连接可能卡在异常状态,后续查询直接报 Cannot execute queries while other unbuffered queries are active——这不是事务问题,是连接管理问题。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











