FrankenPHP Worker模式下Doctrine连接池易耗尽,因每个worker进程独占池且并发处理多协程,若max_connections未按worker数×单worker并发上限×1.2重新计算,叠加禁用健康检查,将触发“Too many connections”或连接超时。

FrankenPHP 的 worker 模式下,Doctrine 连接池会持续复用连接,但默认配置极易在高并发时耗尽——max_connections 不是设得越高越好,而要匹配 FrankenPHP 的 worker 数量与每个 worker 的并发能力。
为什么 FrankenPHP + Doctrine 连接池容易耗尽
FrankenPHP worker 是常驻进程,每个 worker 可并行处理多个请求(取决于 max_requests_per_worker 和协程调度),而 Doctrine 默认连接池不感知 worker 并发模型:它按“每个请求分配一个连接”设计,但 worker 内部实际可能同时运行 10+ 个协程,每个都尝试从池中取连接。若 max_connections: 50,而你启了 8 个 worker,每个 worker 最大并发 12,理论峰值连接需求是 96,远超池上限。
常见错误现象:PDOException: SQLSTATE[HY000] [1040] Too many connections;或日志中反复出现 Connection timeout while waiting for a connection from the pool。
- Doctrine 的
pool配置作用于单个 PHP 进程内,FrankenPHP 的每个 worker 是独立进程,彼此不共享连接池 - 启用
PDO::ATTR_PERSISTENT在 worker 模式下反而有害:持久连接不会被池管理,可能绕过空闲回收逻辑,导致连接泄漏 - 数据库端的
max_connections必须 ≥(worker 数 × 单 worker 最大并发 × 安全冗余系数),否则池再大也无意义
Doctrine 连接池参数必须重算
不能直接沿用 FPM 环境的配置。关键参数需按 FrankenPHP 实际部署规模反推:
立即学习“PHP免费学习笔记(深入)”;
-
max_connections应设为:ceil(单 worker 并发上限 × worker 数 × 1.2);例如 6 个 worker、每个最多处理 8 个并发请求,则设为6 × 8 × 1.2 = 58 → 60 -
timeout建议压到5秒以内:worker 模式响应快,长时间等待连接说明池太小或有慢查询阻塞连接释放 -
idle_timeout设为30秒即可:FrankenPHP worker 不会像 FPM 那样频繁启停,空闲连接可稍久保留,但超过 60 秒易被 MySQL 自动断开(受wait_timeout影响) - 务必关闭
PDO::ATTR_PERSISTENT:它和 Doctrine 内置池机制冲突,在常驻进程中会制造“幽灵连接”
验证连接是否真被复用
光看配置没用,得确认连接确实被池化复用,而非每次新建:
- 开启 Doctrine SQL 日志(
dbal: logging: true),观察相同请求是否复用同一连接 ID(日志中connection_id=xxx字段是否稳定) - 在 MySQL 中执行
SHOW PROCESSLIST,对比请求高峰期的活跃连接数是否稳定在max_connections附近,而非持续爬升 - 检查 FrankenPHP 的
worker.status端点(如/status),确认各 worker 的当前并发请求数,与连接池使用率是否匹配
最容易被忽略的是:Doctrine 的连接池只管理“已建立”的连接,不负责健康检查。如果某个连接因网络闪断或 MySQL 主动踢出而失效,池子不会自动剔除它,下次分配就会报错。必须配合 doctrine/dbal v3.8+ 的 health_check 选项或自定义 ping 逻辑,否则耗尽只是时间问题。



















