Laravel 没有数据库连接池,实际依赖 PHP-FPM 进程内 PDO 连接复用;优化重点是避免重复连接、禁用持久连接、正确处理超时重连及谨慎使用 purge()。

连接池不存在,Laravel 用的是 PHP-FPM 进程级连接复用
很多人以为 Laravel 有“数据库连接池”,其实没有。PHP 是无状态的,DB 连接在每次请求结束时自动关闭(除非显式 DB::disconnect()),但底层 MySQLi/PDO 实际上会复用同一 FPM worker 进程内的连接——前提是没调用 close() 且配置了 mysql.allow_persistent = On(不推荐启用)。真正起作用的是连接复用,不是池化。
所以优化方向不是“建池”,而是:避免重复连接、减少连接开销、控制生命周期。
- 确认
DB::connection()->getPdo()在同一次请求中多次调用返回的是同一个 PDO 实例(可测) - 不要在循环里反复调用
DB::connection(),直接复用已获取的连接对象 - 禁用持久连接:
'options' => [PDO::ATTR_PERSISTENT => false](默认就是 false,但有人会误开)
配置里 pool 和 sticky 参数根本不起作用
Laravel 的数据库配置数组里写 'pool' => ['enabled' => true] 或 'sticky' => true 是无效的——这些是 Laravel 9+ 为未来可能的连接池预留的占位键,目前框架源码中完全未读取或使用。硬加上去只会让人误以为启用了某种机制。
sticky 看起来像读写分离的粘性控制,但它只在 DatabaseManager 的 getDefaultConnection() 中被检查,而该逻辑仅用于测试模拟,并不参与真实连接路由。
- 读写分离必须靠
'read'/'write'配置块 + 自定义Connection类或中间件控制 - 想让事务期间所有查询走写库?得手动调用
DB::connection('write'),不能依赖配置里的sticky - 检查 vendor/laravel/framework/src/Illuminate/Database/DatabaseManager.php 源码,搜索
sticky,你会发现它只出现在注释和单元测试里
长任务里连接超时断连,DB::reconnect() 不是万能解
队列任务、Swoole 或长时间运行的 Artisan 命令中,MySQL 默认 wait_timeout=28800(8 小时),但实际网络设备或云数据库常设为 300 秒。一旦连接空闲超时,下次查询会抛出 PDOException: MySQL server has gone away。
DB::reconnect() 只是重新建立新连接,并不会自动重试原 SQL;如果是在事务中,还可能破坏一致性。
- 更稳妥的做法是捕获
Illuminate\Database\QueryException,检查$e->getPrevious()是否含"server has gone away",再决定是否重试 - 对非关键查询,加
try/catch+ 降级逻辑(如返回缓存值)比盲目重连更合理 - 在
config/database.php的mysql配置里加'options' => [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION],确保异常不被静默吞掉
多数据库切换时,DB::purge() 容易清错连接
调用 DB::purge('mysql') 会从 DatabaseManager 的内部容器中移除名为 mysql 的连接实例。但如果项目同时用了 mysql 和 mysql2,又在某个地方写了 DB::connection('mysql2')->...->getPdo(),再执行 DB::purge('mysql'),看似安全——但注意:Laravel 的连接名是字符串键,purge() 不校验连接是否真实存在,也不区分大小写或前缀。
最危险的是,在中间件或 ServiceProvider 中全局调用 DB::purge(),会导致后续请求首次访问数据库时重建连接,反而增加延迟。
- 只在明确需要重置某连接时才调用,比如测试环境 tearDown 后清理
- 避免在
AppServiceProvider::boot()中调用purge() - 检查
DB::getConnections()返回的数组,确认你要 purge 的键确实存在且唯一
连接管理真正的复杂点不在配置开关,而在请求生命周期与连接状态的耦合。一个 DB 查询报错,可能是连接断了,也可能是事务没提交、锁没释放、甚至只是日志里漏打了 DB::enableQueryLog()。别急着改配置,先看日志里那条失败 SQL 前后发生了什么。










