Laravel 数据库连接不自动释放是因为 PDO 无连接池且依赖 PHP 请求生命周期;在 Swoole、Octane 或队列 worker 等长生命周期环境中需手动调用 DB::disconnect() 清理,禁用持久化连接并验证 MySQL 的 Sleep 连接状态。

为什么 Laravel 的数据库连接不自动释放?
因为 Laravel 默认用的是 PHP 的 PDO,而 PDO 本身没有连接池概念,更不会主动回收空闲连接——它只在 PHP 请求结束时才关闭连接。如果你用的是长生命周期环境(比如 Swoole、RoadRunner 或队列 worker),PDO 连接会一直挂着,直到超时或进程重启。
常见现象:Too many connections 报错、MySQL 的 show processlist 里大量 Sleep 状态连接、内存缓慢上涨。
- PHP-FPM 场景下问题不明显,每个请求是独立生命周期
- Swoole / Octane / 队列 worker 中必须手动干预
- 不是 Laravel “忘了关”,而是它默认依赖 PHP 生命周期管理
怎么让 Laravel 主动清理空闲数据库连接?
核心思路:在每次请求/任务结束后,显式调用 DB::purge() 或 DB::reconnect(),触发底层 PDO 实例销毁。但要注意——不能无脑 purge,否则下次查询会重建连接,反而增加开销。
- 对 Swoole:在
OnRequest或OnWorkerStop回调里调用DB::purge('mysql') - 对队列 worker:在
JobProcessed事件监听器中执行DB::disconnect('mysql') - 避免在中间件里频繁调用
DB::reconnect(),它会强制重连,可能打断事务 - 推荐优先用
DB::disconnect()而非DB::purge(),前者只断开连接,后者还会清空连接实例缓存
Laravel + Swoole 下连接泄漏的典型配置坑
很多人开了 db.persistent: true 或在 config/database.php 里加了 PDO::ATTR_PERSISTENT => true,结果连接数爆炸。持久化连接在 Swoole 下完全不可控,必须禁用。
- 确认
config/database.php中 MySQL 配置不含'options' => [PDO::ATTR_PERSISTENT => true] - 检查
.env是否误设DB_PERSISTENT=true(Laravel 官方不支持该变量,但自定义配置可能读取) - Octane 默认启用连接复用,需配合
--max-requests=500或监听WorkerStopping事件做清理 - 使用
DB::getPdo()打印句柄 ID,可验证同一请求内是否复用了连接
如何验证连接是否真被回收?
别只看日志或内存,直接查 MySQL 服务端状态最可靠。重点观察两个指标:当前活跃连接数、Sleep 连接的平均存活时间。
- 执行
SHOW STATUS LIKE 'Threads_connected';查总连接数 - 执行
SELECT * FROM information_schema.PROCESSLIST WHERE COMMAND = 'Sleep';看空闲连接 - 在 Swoole worker 启动/停止时抓一次快照,对比
Id和Time字段变化 - 如果
Time持续增长且不归零,说明disconnect()没生效,大概率是调用时机不对或连接被其他地方 hold 住了
真正难的不是写那行 DB::disconnect(),而是确定它在哪一刻执行、对哪个连接实例生效——尤其是在多数据库、多连接名、封装了 DB 访问层的项目里,容易漏掉某个分支。


















