PHP脚本中断(如exit/die/dd())不会自动回滚事务,MySQL仅在连接异常断开时隐式回滚,但不保证可靠;真正可靠的回滚必须显式调用rollback()或使用Db::transaction闭包并确保异常穿透。

PHP脚本执行中断时,事务不会自动回滚,除非 PDO 连接在脚本结束前被显式关闭且底层驱动支持连接级清理——但 MySQL(InnoDB)实际不保证这种行为,绝大多数情况下中断 = 数据已提交或处于不可预测状态。
脚本 exit/die/dd() 中断后事务状态不可靠
调用 exit()、die() 或 ThinkPHP 的 dd() 会立即终止脚本,PDO 连接未收到 rollback() 指令,也不触发自动回滚机制。此时:
- 若事务尚未
commit(),MySQL 通常会释放连接并回滚未完成事务——但这依赖于 MySQL 的 wait_timeout 和连接池行为,不是 PHP 或 PDO 的保证 - 若使用连接池(如宝塔面板的 PHP-FPM 子进程复用连接),该连接可能被下一个请求复用,而前一个事务仍标记为 active,导致后续报错
There is no active transaction -
register_shutdown_function无法捕获未被捕获的异常或exit(),它只在脚本“自然结束或致命错误”时运行,且拿不到PDOException实例
PHP 异常未捕获 ≠ 自动回滚
只有在 Db::transaction() 闭包内抛出未被捕获的 \Exception 或 \Throwable(PHP 8.0+ 必须包含后者),ThinkPHP 才会调用 rollback()。但以下情况均不触发:
- 闭包内写了
try/catch却没re-throw,哪怕Log::error()记了日志,框架也认为“你已处理”,直接commit() - 抛出的是
TypeError、FatalError等\Throwable子类,但闭包外只 catch\Exception,PHP 8+ 就静默忽略,事务照常提交 - 使用了
set_exception_handler()全局兜底,却没在 handler 里手动Db::rollback(),框架无感知
MySQL 层面的隐式提交会让 rollback 失效
即使 PHP 层没中断,只要事务中执行了 DDL 语句(如 CREATE TABLE、ALTER TABLE、DROP INDEX),MySQL 会立即隐式提交当前事务,后续 rollback() 对此前所有操作无效:
立即学习“PHP免费学习笔记(深入)”;
$pdo->beginTransaction();
$pdo->exec("INSERT INTO user (name) VALUES ('A')");
$pdo->exec("CREATE TABLE tmp_log (id INT)"); // ← 此处已隐式 commit
$pdo->rollBack(); // ← 不会撤销 INSERT
这类操作必须剥离到事务外单独执行,不能混在同一业务逻辑块中。
真正可靠的回滚只来自显式控制流
不要依赖“脚本挂了系统会善后”。可靠做法只有两种:
- 闭包模式:坚持用
Db::transaction(function () { ... }),确保闭包内不写try/catch,所有异常穿透出去 - 手动模式:用
Db::startTrans()+try/catch显式包裹,且catch块中必须调用Db::rollback()并throw $e或return false(TP 6.0.10+ 支持) - 无论哪种,都得确认表引擎是
InnoDB(SHOW CREATE TABLE user查ENGINE=InnoDB),MyISAM 下任何rollback()都是空操作
最易被忽略的一点:调试时用 dd() 或 var_dump() + exit 中断流程,比写错 SQL 更容易让事务卡死——因为连接没释放,下个请求就可能复用一个“以为在事务中、其实已失效”的连接。



















