Laravel 默认不带连接池,因其DB门面和Eloquent底层基于PDO,而PDO本身无连接池机制,每次DB::connection()默认新建连接;高并发下易导致“Too many connections”错误。

为什么 Laravel 默认不带连接池
Laravel 的 DB 门面和 Eloquent 底层用的是 PHP 的 PDO,而 PDO 本身不维护连接池——每次 DB::connection()(或首次查询)都会尝试新建一个连接,除非启用了 PHP-FPM 的持久连接(mysqlnd + pdo_mysql.default_socket 或 pdo_mysql.persistent),但那不是应用层连接池,也不可控、不统计、易泄漏。
真实场景下,高并发时你看到的“连接数暴涨”“Too many connections”错误,往往不是因为业务 SQL 慢,而是 Laravel 每次请求都新建连接、用完不显式释放,又没复用机制。
- PHP-FPM 子进程结束时,PDO 连接才真正关闭;若子进程常驻(如
pm = static),连接可能复用,但无法感知、无法计数、无法超时回收 -
DB::reconnect()不是重用,是断开再连,反而加重负担 - Laravel 的
DB::disconnect()只是标记连接为“已关闭”,PDO 实例仍存在,下次查还会自动重连
怎么统计实际创建的连接次数
最可靠的方式是拦截底层 PDO 构造行为,在连接建立时埋点。Laravel 允许通过自定义 PDO 类或包装 ConnectionFactory 实现,但更轻量的做法是 Hook Illuminate\Database\Connectors\Connector 的 createConnection() 方法。
在 AppServiceProvider::boot() 中打补丁:
// app/Providers/AppServiceProvider.php
use Illuminate\Database\Connectors\Connector;
use Illuminate\Support\Facades\DB;
$original = Connector::class . '::createConnection';
Connector::macro('createConnection', function (...$args) use ($original) {
// 计数器存到 Laravel 的全局缓存(非持久,仅当前请求生命周期)
$count = app('cache')->get('db_connection_count', 0);
app('cache')->put('db_connection_count', $count + 1, 3600);
// 也可写日志或发到 StatsD(如果已有监控链路)
\Log::debug('DB connection created', ['count' => $count + 1]);
return $this->{$original}(...$args);
});
- 该统计只对「新创建」有效,不包含复用(比如 FPM 持久连接),所以它反映的是 Laravel 主动发起的新连接动作次数
- 不要用
static $count,PHP-FPM 多进程下不共享;也不要存 Redis,会干扰性能采样 - 注意:Laravel 9+ 中
Connector已被重构为MySqlConnector等具体类,需针对驱动替换,例如监听Illuminate\Database\Connectors\MySqlConnector
怎么让连接真正复用(非持久化方案)
不用改 PHP 配置、不依赖 mysqlnd 持久化,靠 Laravel 自身机制实现“请求内复用”:强制复用同一个 Connection 实例,避免多次调用 DB::connection() 创建新实例。
- 永远用
DB::connection('mysql')而不是DB::connection()(后者每次调用都 new 一个新实例) - 把连接实例绑定到容器中,生命周期设为 singleton:
// 在 AppServiceProvider::register()
$this->app->singleton('db.mysql.reusable', function ($app) {
return $app->make('db')->connection('mysql');
});
// 使用时
$db = app('db.mysql.reusable');
$db->table('users')->first();
- 这样整个请求周期内只初始化一次连接对象,后续所有查询都走同一个 PDO 实例(前提是没手动调
disconnect()) - 注意:Eloquent 模型默认仍走自己的连接解析逻辑,所以模型操作不会自动受益;如需统一,得重写
getConnection()或用DB::connection('mysql')显式指定 - 这个方法不解决跨请求复用,但它把“单请求连接爆炸”问题掐死在源头
连接池真要上,得换底层驱动
Laravel 原生不支持连接池,硬加只能靠外部组件。目前唯一成熟、低侵入、可监控的选择是 swoole + co-mysql 或 spiral/database,但前提是你已用 Swoole 替换 PHP-FPM。
例如用 spiral/database 配合 Laravel 的 DatabaseManager 替换:
// composer require spiral/database // 然后在容器中 bind 自定义 DatabaseManager,用其 Pool 实现连接复用与统计
- 它的
Pool支持 maxIdle、maxActive、idleTimeout,还能暴露$pool->stats()返回created/acquired/released等字段 - 但代价是放弃 PDO,意味着部分 DBAL 功能(如 Schema Builder 的某些方言)可能异常,且所有查询必须协程安全
- 如果你还在用 Apache / FPM,别折腾连接池——先优化连接复用和统计,比盲目上池子实在得多
连接复用的关键不在“池”,而在“别重复创建”。统计的意义也不是为了凑数字,而是定位哪段代码在反复 DB::connection() 却没复用——那个地方,往往还藏着 N+1 和事务未关闭。


















