symfony在frankenphp worker模式下不自动重连,是因为doctrine dbal的健康检查仅在显式ping时触发且默认只用于cli,而worker中pdo常驻内存,mysql超时后连接静默失效,请求时直接抛出2006异常。

默认不重连,必须手动干预;否则 worker 进程里 PDO 连接失效后会一直报 PDOException: SQLSTATE[HY000] [2006] MySQL server has gone away。
为什么 Symfony 在 FrankenPHP worker 模式下不自动重连
Symfony 的 Doctrine DBAL 默认使用连接池(Connection 实例复用),但它的“健康检查”只在 ping() 被显式调用时触发,且仅限 CLI 环境下由 doctrine:database:ping 命令驱动。Web 请求场景中,Doctrine 从不主动 ping —— 它假设每次请求都是全新生命周期,连接由 PHP-FPM 进程自动重建。FrankenPHP worker 模式打破了这个假设:PDO 实例常驻内存,MySQL 的 wait_timeout(默认 8 小时)一过,连接就静默断开,下次 executeStatement() 直接抛异常。
常见错误现象:
- 接口偶发 500,日志里反复出现
PDOException: SQLSTATE[HY000] [2006] MySQL server has gone away - 重启 FrankenPHP 后一切正常,几小时后又开始报错
-
doctrine:database:ping命令返回 success,但 Web 请求仍失败 —— 因为 CLI 和 worker 是两个独立连接实例
三种可行的重连方案及取舍
不能靠改 wait_timeout 治标,得让应用自己感知并重建连接。目前只有三个路径可走:
-
方案一:启用 DBAL 的
auto_commit+ping钩子 —— 在kernel.request事件里对$connection->ping(),失败则$connection->close()+$connection->connect()。简单但每次请求都多一次 round-trip,压测时 RT 上浮 1–2ms -
方案二:用
doctrine/dbal3.8+ 的RetryableException机制 —— 配置options中的driverOptions加'pdo' => ['ATTR_ERRMODE' => \PDO::ERRMODE_EXCEPTION],再配合自定义ExceptionConverter捕获2006并重试。需要升级 DBAL,且重试逻辑要小心避免死循环 -
方案三:换连接管理器(推荐) —— 引入
spiral/database或轻量robmorgan/phinx的连接封装层,在__get()或getConnection()里做ping()+ 自动 reconnect。侵入小、可控性强,适合已上线项目
FrankenPHP worker 模式下必须关掉的 Doctrine 配置
以下配置项在常驻进程里会引发资源泄漏或状态错乱,务必检查并禁用:
-
doctrine.dbal.url中不要带?charset=utf8mb4以外的查询参数 ——serverVersion、defaultTableOptions等静态配置若含运行时变量(如$_SERVER['HOSTNAME']),会在 worker 初始化时固化,后续无法更新 -
doctrine.orm.auto_generate_proxy_classes必须设为false—— 开启后每次请求都尝试写缓存文件,worker 多线程并发写同一目录极易出错 -
doctrine.dbal.logging设为false——DebugStack实例常驻会导致内存持续增长,日志对象不释放 -
doctrine.dbal.options里的mapping_types若动态注册(如通过 bundle 扩展),需确保只在容器编译期执行一次,不能放在Kernel::configureContainer()的每次请求回调里
验证重连是否生效的关键检查点
光看日志不够,得抓真实行为:
- 在
getConnection()调用前后加var_dump($connection->isConnected()),确认断连后确实重建了新连接(resource id变化) - 用
tcpdump -i lo port 3306抓包,看到RST后紧跟着新的SYN,说明重连触发成功 - 查 MySQL 的
SHOW PROCESSLIST,观察连接数是否稳定在合理范围(比如 5–20),而不是随请求累积到几百 - 故意把 MySQL
wait_timeout改成 30 秒,然后写个循环请求接口,确认 30 秒后不再报错 —— 这是最直接的验证方式
最易被忽略的是:重连逻辑必须包裹在 try/catch (PDOException $e) 内部,且 catch 里不能直接 throw $e,否则上层 Symfony 的异常处理器会绕过你的修复逻辑。FrankenPHP 的常驻特性放大了所有“一次性假设”,每个连接、每个缓存、每个全局状态,都得重新审视生命周期。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











