Laravel本身没有数据库连接池,PHP-FPM无状态模型决定连接无法跨请求复用;config/database.php中pool和sticky配置无效,仅Swoole/RoadRunner等常驻内存环境才支持真正连接池。

Laravel 本身没有数据库连接池,无论你怎么配 config/database.php,只要跑在 PHP-FPM 下,就不可能有真正意义上的连接池。 这不是配置遗漏,而是由 PHP 的无状态进程模型决定的——连接无法跨请求复用,所有“池化”宣传要么是误导,要么只在 Swoole/RoadRunner 等常驻内存环境中生效。
PHP-FPM 场景下别碰 pool 和 sticky 配置项
很多人在 MySQL 配置块里硬加上 'pool' => ['enabled' => true] 或 'sticky' => true,结果毫无效果。这不是你写错了,是 Laravel 框架压根不读这些键:翻 vendor/laravel/framework/src/Illuminate/Database/DatabaseManager.php 源码,搜索 sticky,只会在注释和测试里出现;pool 更是纯占位字段,逻辑中零调用。
所谓“复用”,仅限单次请求内:DB::table('users')->first() 和后续同连接的 ->where()->get() 共享一个 PDO 实例;但请求一结束,连接就关闭(除非开了危险的持久连接)。
- 禁用
PDO::ATTR_PERSISTENT => true:它会让 FPM worker 复用底层 TCP 连接,但极易引发事务残留、用户变量污染、临时表未清理等问题 - 不要信“连接池扩展”:所有基于 FPM 的第三方包,本质只是连接缓存或代理,MySQL 侧的
SHOW PROCESSLIST连接数仍随并发线性上涨 - 压测时出现
SQLSTATE[HY000] [1040] Too many connections,根源是FPM 子进程数 × 并发请求数打满了 MySQL 的max_connections,不是 Laravel “没配好池”
Swoole 场景下连接池才真正起作用
只有当你已切换到 Swoole HTTP Server(如通过 swooletw/laravel-swoole 或 Laravel Octane),每个 Worker 进程长期存活,协程才能调度一组长连接复用,这时“连接池”才有实际意义。
但必须满足几个硬性前提:
-
host必须写成127.0.0.1,不能写localhost,否则 Swoole 会走 Unix socket,连接池直接失效 - 显式启用持久连接:
'persistent' => true,并在options中补上PDO::ATTR_PERSISTENT => true - 加超时控制:
'options' => [PDO::ATTR_TIMEOUT => 5],防个别慢查询卡死整个协程 - 读写分离需手动控制:
sticky => true在 Swoole 下才被部分协程驱动识别,但事务中读操作仍可能落到从库——务必用DB::connection('write')显式指定
比“加池”更有效的三板斧
与其纠结不存在的连接池,不如直面 FPM 场景下的真实瓶颈:短连接高频创建、空闲连接堆积、慢查询拖垮整体。
- 调低 MySQL 的
wait_timeout(建议 60–120 秒):避免 PHP 进程崩溃后连接在 MySQL 侧长期滞留 - 队列任务里禁用循环查单条:
Model::find($id)改成Model::whereIn('id', $ids)->get()->keyBy('id'),一次查完再 PHP 映射 - 强制读写分离时,确保
read和write都配了sticky => true,否则事务中后续读可能落到从库,引发数据不一致 - 验证是否真复用:启动服务后执行
SHOW PROCESSLIST,连接数应稳定在 Worker 数量级,而非随请求峰值飙升
最容易被忽略的是:Swoole 下的连接池失效往往不是代码问题,而是 localhost 写法触发了 Unix socket 路径解析,导致所有连接绕过池管理直连——这个细节在日志里完全不报错,只能靠 SHOW PROCESSLIST 和网络抓包确认。


















