事务残留本质是pdo连接复用且未清理状态,非frankenphp缺陷;需将$pdo初始化移入请求循环、每次结束前检查intransaction()并显式rollback、禁用持久连接、调用disconnect()重置连接。

事务残留本质是连接未清理,不是 FrankenPHP 的 bug。它不会自动帮你 rollback 或 close 连接,只要 PDO 实例被复用、且上次请求没显式结束事务,下个请求就可能继承那个未提交/回滚的状态,导致死锁、数据不一致或“事务已处于活动状态”错误。
检查 PDO 是否在 worker 内被重复复用
传统 FPM 每次请求新建 PDO 实例,事务自然隔离;FrankenPHP worker 模式下,如果代码里把 $pdo = new PDO(...) 放在循环外(比如 preload 阶段或入口文件顶部),那所有请求共享同一个连接对象 —— 事务状态就跨请求了。
- 用
var_dump($pdo->inTransaction())在每次请求开头检查,若返回true,说明上一个请求的事务没结束 - 确认框架 ORM(如 Laravel 的
DB::connection())是否配置了'sticky' => false和PDO::ATTR_PERSISTENT => false - 禁用
PDO::ATTR_PERSISTENT:worker 模式下持久连接反而容易出问题,除非你有完整连接池管理逻辑
每次请求结束前必须显式清理事务状态
不能依赖脚本退出或异常终止来自动 rollback —— worker 协程会复用,连接不会销毁。
- 在请求生命周期末尾(例如 Symfony 的
kernel.terminate事件、Laravel 的app.terminating事件)调用:$pdo->rollBack()(如果inTransaction()为 true) - 更稳妥的做法是:无论是否出错,统一执行
if ($pdo->inTransaction()) { $pdo->rollBack(); } - 避免只在 try/catch 的
catch块里 rollback —— 正常流程也可能中途 return,遗漏清理
数据库连接本身也要重置或关闭
事务清理只是表象,深层问题是连接对象长期持有状态。即使 rollback 了,连接仍可能卡在某个 isolation level、临时表未删、会话变量未清。
- Laravel 用户应在请求结束时调用:
DB::disconnect()(非reconnect),强制释放底层 PDO 实例 - ThinkPHP 用户对应:
Db::close() - 原生 PDO 场景:请求处理完后设
$pdo = null,再让 GC 回收(配合gc_collect_cycles()) - 检查
SHOW PROCESSLIST或netstat -an | grep :3306,确认连接数不随请求数线性增长
别让事务控制逻辑逃逸到全局作用域
有些老项目会把事务 start/commit/rollback 封装成全局函数,甚至用 static 变量标记状态(如 static $inTransaction = false)。这类写法在 worker 下必然污染后续请求。
- 禁止在任何类的
static属性里存事务状态 - 避免用
register_shutdown_function()自动 rollback —— worker 模式下 shutdown 函数只在进程退出时触发,不是每次请求 - 事务边界必须严格绑定请求上下文,推荐用中间件或事件监听器封装,而非函数式工具
最易被忽略的一点:MySQL 的 autocommit 模式默认是开启的,但一旦你执行了 BEGIN 或 START TRANSACTION,它就进入手动模式,且这个状态会一直留在连接上,直到 commit/rollback 或连接断开。FrankenPHP 不会替你“重置连接”,你得自己动手。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











