Hyperf中数据库连接未释放不会直接导致PHP内存溢出,但会引发连接池耗尽→协程堆积→PDO缓冲结果集累积→最终触发Allowed memory size exhausted;根本原因是PDO默认启用缓冲查询,需禁用PDO::MYSQL_ATTR_USE_BUFFERED_QUERY并改用cursor()或手动fetch循环,同时确保事务正确提交/回滚、闭包不捕获连接、限制结果集大小及收紧连接池参数。

Hyperf 中数据库连接未释放,本身不会直接导致 PHP 内存溢出(OOM),但会引发连锁反应:连接池耗尽 → 请求排队等待 → 协程堆积 → 大量未释放的 PDO 结果集缓冲 → 最终触发 Fatal error: Allowed memory size of ... exhausted。真正吃掉内存的,往往是那些“已执行 SQL 但没 fetch 完、也没关闭”的关联查询结果。
关掉 PDO 缓冲,强制流式读取
PDO 默认开启 PDO::MYSQL_ATTR_USE_BUFFERED_QUERY = true,哪怕你只调用一次 fetch(),整个连表结果(比如 10 万 × 5 行)也会被一次性加载进内存。这不是代码逻辑问题,是驱动默认行为。
- 在
config/autoload/database.php的 MySQL 配置中显式禁用缓冲:'options' => [PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false] - 避免使用
Db::select()、Model::get()或all()—— 它们底层仍走缓冲模式 - 改用原生游标方式:
Db::query('SELECT ...')->cursor()(Hyperf 3.x+ 真正穿透)
或手动while ($row = $statement->fetch()) { ... }
检查连接是否真归还了连接池
连接没释放,不一定是忘了 close(),更多是“用了连接但没让协程上下文主动交还”。常见于:
- 事务块中异常跳出,未执行
$db->commit()或$db->rollback() - 在
try中执行查询后,finally里没做任何清理,尤其配合sleep()、长循环或阻塞操作时 - 闭包
use ($connection)捕获了连接对象,延长其生命周期,GC 无法回收
验证方法:登录数据库执行 SHOW PROCESSLIST,观察大量 Sleep 状态连接持续存在,且数量随请求增长——说明连接未归还。
限制结果集大小,提前拦截风险
连表查询容易让行数指数级膨胀(如订单 × 子订单 × 商品),即使关了缓冲,单次拉取 50 万行仍可能压垮协程栈或触发 GC 延迟。
- 所有分页接口必须带
LIMIT,禁止SELECT * FROM ... JOIN ...无限制查询 - 对导出类任务,改用基于主键 ID 的分段拉取:
WHERE id BETWEEN ? AND ?+ 游标推进 - 在 DAO 层统一加保护:查询前校验预计影响行数(通过
EXPLAIN或预估统计),超阈值(如 5 万行)直接拒绝
配合连接池参数收紧空闲生命周期
即使代码层“看起来”释放了连接,若连接池配置宽松,Sleep 连接仍会长期滞留,间接加剧内存压力(每个连接背后有 PDO 对象、结果集引用等)。
- 设置
pool.max_idle_time => 30.0(单位秒),让空闲连接及时销毁 - 调低
pool.wait_timeout => 3.0 ~ 5.0,避免协程长时间卡在获取连接上 - 将
pool.max_connections设为合理上限(如 50~80),防止突发流量打爆内存 - 注意:
max_idle_time对 Redis 连接池无效,它用的是idle_timeout


















