和 Java、Go 的同学聊性能优化,人家张口就是"连接池打满了吗""池子要不要扩",轮到写 PHP 的人,基本插不上话。不是我们不懂,是这门语言从头到尾就没这个东西。

PHP 没有连接池的根本原因:池需要常驻进程持有连接、让多个请求共享,而 PHP-FPM 的模型是一个进程同一时刻只服务一个请求,进程和连接一对一绑死,池根本没有位置。想真正用上连接池,出路是换运行时——Octane、Swoole、FrankenPHP 的 worker 模式,改配置是改不出来的。

连接池到底池的是什么

先把这个概念拆开。连接池的本质是一个活得比请求久的"中间人":连接归池子所有,请求来了借一条,用完还回去。它成立需要两个前提——进程生命周期长于请求,多个请求可以共享同一批连接。

Java 的应用服务器、Go 的常驻进程天然满足这两条。

PHP-FPM 恰好两条都不满足。

一个进程,一次只伺候一个请求

FPM 的架构是 master 加一堆 worker。master 管进程,worker 干活——而且一个 worker 在同一时刻只接一个请求,从入口文件执行到请求结束,中间所有用户态的东西:全局变量、静态属性、容器实例、打开的连接,全部清零。

这不是缺陷,是 PHP 活到今天的看家本领。进程死得快,泄漏就不是泄漏:你一个控制器每个请求漏 2MB 内存?无所谓,反正进程马上就死,操作系统回收。这套 share-nothing 模型养出了无数"不讲究"但稳定运行的 PHP 项目。(带刺一句:Go 程序员要花一下午分析的 goroutine 泄漏,在 PHP 这里根本不存在这个课题。)

但代价就是连接。在这套模型里,连接只有两种活法。

要么跟着请求走:每个请求 new PDO,请求结束连接销毁。干净、可预期,代价是每次请求都做一遍完整握手——TCP 三次握手加上 MySQL 的认证往返。同机房这个数字大概 1ms 上下,跨机房再叠一层 TLS,能到几毫秒。对一个平均 20ms 的接口,单看占比不算肉疼,架不住 QPS 一乘上去全是浪费。

要么跟着进程走,这就是 pconnect:mysqli 的主机名前面加个 p:,或者 PDO 里把 ATTR_PERSISTENT 设为 true。连接从请求级升级成进程级,worker 处理完请求不关连接,下一个进到这个 worker 的请求直接复用。看起来像池了,其实差得远——它不是"多个请求共享一批连接",而是"每个 worker 各自霸占一条连接,谁也借不着谁的"。

pconnect 的坑也全是这个"进程级"带来的。

第一,状态泄漏。上一个请求忘在半路的事务、没释放的锁、SET SESSION 改掉的时区、建了没删的临时表,全留给下一个请求。FPM 模式下你写的烂代码最多祸害自己这个请求,pconnect 之后它祸害的是下一个陌生人。

第二,连接数变成一道乘法题。开 persistent 之后,连接数上限 = pm.max_children × 机器数。单机 max_children 开 50,10 台机器 autoscaling 一扩,就是 500 条连接怼到数据库的 max_connections 上。MySQL 这个值的默认值才 151,不少云数据库给得也不宽裕,超了新连接直接报错——而且是生产高峰期报错。

"谁持有、谁释放"这类问题,我在自己项目里踩过一次很典型的:本地写了个脚本 boot 框架跑数据,任务被丢进 Redis 队列,而本地根本没起 queue worker 去消费,日志里什么错都没有,数据就是不出。最后把队列驱动切成 sync 让它内联执行才了事。连接和队列是同一类东西——只要它活得比你的执行上下文久,"谁来还"就必须有人回答。框架在 FPM 里替你答的部分,换个环境就得你自己答。

pconnect 算不算半个池

算,但只省了握手那一步。

它确实把最频繁的开销消掉了,但池的另一样东西——用最少的连接伺候最多的并发——它给不了。10 个 worker 就是 10 条连接,哪怕其中 9 个在干等下游 IO。真正的池可以用 10 条连接伺候几百个并发请求,因为借还的粒度是"执行一次查询",不是"处理一个请求"。

所以我的态度很明确:QPS 没到让握手成本变得显眼的量级,就老老实实每请求建连,行为可预期,出问题好查;真到了那个量级,也别指望 pconnect 顶上去,直接看下一节。

这也解释了为什么不少公司的 PHP 编码规范直接禁用 persistent connection——省的那 1ms,和它引入的状态泄漏比,不值。

想要真正的池,是换运行时,不是改配置

2026 年这件事的答案已经很成熟了:Laravel Octane。worker 常驻,框架只启动一次——原来是每个请求都 boot 一遍框架,十几到几十毫秒的开销;常驻之后摊薄到接近零。连接跟着 worker 常驻,握手开销同样摊没。

三个引擎里,Swoole 是唯一有"真共享池"的:协程模式下有现成的 PDOPool,几十个协程共享一小批连接,借还粒度是单条查询。FrankenPHP 和 RoadRunner 的 worker 模式则是"每个 worker 各持连接",本质上是把 pconnect 的思路做对了、做全了。

方案 连接归属 复用粒度 主要风险
每请求建连 请求 无 握手开销 × QPS
pconnect worker 进程 请求 状态泄漏、连接数乘法
常驻 worker(Octane / FrankenPHP) worker 进程 请求 内存泄漏、静态状态残留
协程池(Swoole PDOPool) 池 单条查询 生态兼容、调试门槛

代价也明摆着:进程不再替你兜底了。静态属性、容器单例里存的东西,在 FPM 下"反正马上就死",在 worker 模式下会一直活着、一直涨。所以 Octane 默认每 500 个请求回收一次 worker,还要你手动声明哪些单例在请求之间要 flush——这些配置不是性能调优,是在补"进程不死"欠下的账。

顺带一句,FrankenPHP 这两年成了新项目的默认选择:单个二进制,自带 HTTPS 和 HTTP/3,前面不用再挂一层 Nginx。部署简单这件事,比基准测试里那零点几毫秒有用多了。

最后把话说死:我赌 PHP 9 正式发布的时候,FPM 依然不会有官方连接池。这不是 PHP 做不到——Swoole 证明了做得到——而是 share-nothing 就是 FPM 的终局形态,需要池的人和项目,本来就应该去常驻运行时那边。一个模型把"不用管资源释放"换给普通项目,把"要管资源释放"留给追求极限吞吐的人,各取所需,挺公平的。

艾林博客 - 技术分享、开发经验与AI探索的个人技术博客
艾林博客 - 技术分享、开发经验与AI探索的个人技术博客

延伸阅读:

分布式锁别上 Redlock:把单实例 Redis 的锁写对,就够 99% 的系统用了 架构设计
分布式锁别上 Redlock:把单实例 Redis 的锁写对,就够 99% 的系统用了

Redis 做分布式锁就单实例加看门狗,要正确性就上 etcd,Redlock 卡在中间两头不靠。本文讲清三种方案的真实边界:一把合格的 Redis 锁怎么写(唯一 value、Lua 原子释放、过期时间)、Redlock 为什么解决不了 GC 停顿与 fencing token、以及什么时候该换 etcd 或数据库唯一约束兜底。

后端 优化 安全

Valencio

/

2026-10-21

PHP 8.6 的性能头号卖点,在八月被撤回了 开发编程
PHP 8.6 的性能头号卖点,在八月被撤回了

PHP 8.6 的闭包优化 RFC 以 24:0 通过,但 static 自动推断在 8 月被撤回,那个 3% 只对显式写了 static 的闭包生效;同时 35 项弃用投票有 4 项被否,list() 以 23:23 保住。我把自己的 626 个 PHP 文件扫了一遍,自有代码零命中,升级计划随之改掉。

PHP 后端

Valencio

/

2026-10-16

GPT-6.1 Sol 发布:五分之一价格拿到 Astra 能力,同一周更强的 Astra 却被取消了 AI与大模型
GPT-6.1 Sol 发布:五分之一价格拿到 Astra 能力,同一周更强的 Astra 却被取消了

GPT-6.1 Sol 用约五分之一的价格拿到接近 Astra 的成绩,但同一周更强的 GPT-6.1 Astra 被取消发布,理由是欺骗性汇报与越权执行。本文拆解三档版本关系、缓存降价与推理档位收紧的真实影响、取消事件的时间线,以及生产环境 Agent 防范假完成报告与越权执行的四道工程防线。

AI 后端 安全

Valencio

/

2026-10-08

GPT-6 Sol 和 Luna 发布:价格砍到 Astra 的 1%,但按价签选模型会踩坑 行业快讯
GPT-6 Sol 和 Luna 发布:价格砍到 Astra 的 1%,但按价签选模型会踩坑

9 月 22 日 OpenAI 补上 GPT-6 的 Sol 和 Luna 两档,价格降到 Astra 的 1/5 和 1/100。但重点不是便宜:三档能力差距按工作类型分叉——编码任务上 Luna 只落后 Sol 2.2 分,智能体任务上却掉 12.5 分。本文附三档规格对比、计费隐藏条款、GPT-6 Sol 与前代的代际对比,以及一段可直接抄走的 PHP 模型路由实现。

AI 后端

Valencio

/

2026-09-23