事务回滚失败90%是表引擎非innodb,需用show create table检查engine是否为innodb,myisam等引擎不支持事务,rollback静默无效;修复须alter table engine=innodb并备份,建表时应显式指定engine=innodb。

事务回滚失败,90% 是表引擎不是 InnoDB
直接查表结构:SHOW CREATE TABLE user;,看输出里有没有 ENGINE=InnoDB。如果是 MyISAM 或 MEMORY,事务根本不起作用——ROLLBACK 不报错,但数据早已落盘。
修复方式只有改引擎:ALTER TABLE user ENGINE=InnoDB;(务必先备份);建表时也得显式写上 ENGINE=InnoDB DEFAULT CHARSET=utf8mb4。
注意:ThinkPHP 不会主动校验引擎,也不会报错提示,它只管发 SQL,底层 MySQL 爱理不理。
异常被 try-catch 吞掉,事务就默认 commit 了
Db::transaction() 只对「未被捕获的异常」生效。一旦你在闭包里自己 try { ... } catch (\Exception $e) { } 却没 throw $e,框架压根不知道出错了,会照常执行 commit()。
- ❌ 错误写法:
Db::transaction(function () { try { Db::name('order')->insert([]); } catch (\Exception $e) { Log::error($e); } }); - ✅ 正确写法:去掉
try-catch;或保留但末尾加throw $e; - ⚠️ PHP 8.0+ 还要注意:
\Error(如Fatal error)不会被\Exception捕获,需用\Throwable
Db::startTrans() + rollback() 在 TP5.1+ 基本不可靠
TP 5.1+ 默认连接惰性初始化,Db::startTrans() 可能作用在一个还没真正建立的连接上,而后续 Db::table('x')->insert() 却触发了新连接,导致事务控制和 SQL 执行不在同一个 PDO 实例里。
典型现象:rollback() 执行了但数据还在;commit() 报 There is no active transaction;日志里看不到 BEGIN。
解决办法只有一个:统一用 Db::transaction() 闭包,且闭包内所有操作必须用 Db::name('table') 或模型方法(如 User::create()),禁用 db('table') 助手函数。
跨库、读写分离、嵌套调用都会让事务失效
MySQL 单实例不支持跨 database 的原子事务。哪怕你写了 Db::connect('db1') 和 Db::connect('db2'),它们是两个独立连接,ROLLBACK 只影响当前连接。
读写分离开启后('deploy' => 1),INSERT 走主库,SELECT 可能被路由到从库,事务内查不到刚插的数据——这不是回滚问题,是查询路由错位。
嵌套 Db::transaction() 会被静默忽略,PDO 和 MySQL 都不支持真嵌套。外层事务不会因内层失败而回滚,数据可能部分写入。
最稳妥做法:所有 DB 操作收进一个顶层 Db::transaction() 闭包,别拆、别分层、别跨库。
ENGINE=InnoDB、关掉读写分离、用 Db::name() 替代 db(),再看是否还失效。php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











