doctrine默认连接在frankenphp worker中会出问题,因其采用“请求新建+自动关闭”模型,而worker常驻导致pdo连接不物理断开、堆积sleep状态连接,最终触发mysql too many connections错误。

FrankenPHP 的 worker 模式下,Doctrine 连接不能靠 PDO::ATTR_PERSISTENT 自动复用——它会泄漏连接、累积空闲连接、最终触发 MySQL 的 Too many connections 错误。
为什么 Doctrine 默认连接在 FrankenPHP worker 里会出问题
Doctrine DBAL 默认使用“每次请求新建连接 + 请求结束自动关闭”的模型,这在 PHP-FPM 短生命周期中完全合理;但 FrankenPHP worker 是常驻进程,$connection->close() 被调用后,底层 PDO 实例并未真正断开 TCP 连接(PDO 的 __destruct 不触发物理关闭),而 Doctrine 又默认不复用已关闭的连接对象。结果就是:每个请求都新建一个连接,旧连接堆积在池里不释放。
- 现象:MySQL 的
SHOW PROCESSLIST中出现大量Sleep状态连接,且数量随请求量线性增长 - 根本原因:Doctrine 的
Connection对象未被复用,也未被显式回收到连接池 - FrankenPHP 本身不干预 PHP 层连接生命周期,它只保证进程不退出——连接管理完全交由应用层负责
必须手动启用 Doctrine 内置连接池并禁用自动关闭
Doctrine 从 3.0+ 开始支持原生连接池(doctrine/dbal ≥ 3.7.0),需配合 pool 配置 + 显式复用逻辑,否则 worker 模式下等于没开池。
- 在
config/packages/doctrine.yaml中启用池化:doctrine: dbal: url: '%env(DATABASE_URL)%' options: # 必须关闭自动关闭,否则池失效 doctrine.dbal.connection.disable_auto_close: true pool: max_connections: 20 timeout: 10 idle_timeout: 300 -
disable_auto_close: true是关键开关——它阻止 Doctrine 在请求结束时调用$connection->close(),让连接保留在池中待复用 - 确保你的
DATABASE_URL不含&persist=1或类似参数,PDO 持久连接与 Doctrine 池互斥,混用会导致连接句柄错乱
请求内必须显式获取和释放连接(不能依赖构造注入)
在 worker 常驻上下文中,不能把 Doctrine\DBAL\Connection 当作服务直接注入控制器或仓储——它会被单例容器缓存,导致所有请求共享同一个连接实例,引发并发写冲突或事务污染。
- 正确做法:每次请求通过
ConnectionPool获取新连接实例(逻辑上复用,物理上隔离):$pool = $container->get(ConnectionPool::class); $connection = $pool->get(); // 阻塞获取,超时由 pool.timeout 控制 try { $connection->executeStatement('INSERT ...'); } finally { $pool->put($connection); // 必须放回,否则连接泄露 } - 若用 Doctrine ORM,需绕过默认 EntityManager:它内部仍持有连接引用,建议用
EntityManagerInterface::getConnection()获取池化连接,但要确保不长期持有引用 - 切勿在类属性中保存
$this->connection,worker 生命周期内该属性会跨请求残留
监控连接池状态比调优更重要
FrankenPHP worker 没有请求日志天然标记连接归属,一旦池配置不当,问题会延迟暴露——比如 idle_timeout: 300 在低流量时段会让连接空闲 5 分钟才回收,期间若数据库重启,这些连接就变成僵尸连接,后续请求直接报 MySQL server has gone away。
- 必须定期检查:
SELECT COUNT(*) FROM information_schema.PROCESSLIST WHERE COMMAND = 'Sleep';,值应稳定在pool.max_connections附近,而非持续上涨 - 在 worker 启动时加钩子,用
Doctrine\DBAL\Tools\Console\ConnectionCommand或自定义命令 dump 当前活跃连接数 - 最易忽略的一点:FrankenPHP 的
worker模式默认启用 opcache preload,若 Doctrine 配置文件被预加载,pool参数可能被静态解析一次后不再刷新——修改配置后务必重启 FrankenPHP 进程
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











