PHP7.3 PDO默认缓冲查询导致内存溢出:需显式关闭缓冲、逐行fetch、及时unset变量并避免持久连接与sleep驻留。

PHP7.3 的 PDO 默认是缓冲查询,结果集全进内存
这是最常被忽略的根源:PDO 默认开启 PDO::MYSQL_ATTR_USE_BUFFERED_QUERY,$stmt->execute() 一执行,MySQL 就把整张表或整个结果集一次性发给 PHP,PDO 全部读进内存再封装成数组/对象。哪怕你只用 fetch() 逐行取,数据早就在那儿了。
典型表现:memory_get_usage() 在 query() 后立刻飙升,后续 fetch() 不降反升(因对象构造开销);100 万行订单查出来,内存直接涨几百 MB。
- 显式关闭缓冲:
$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);,之后所有query()都走非缓冲模式 - 必须在
query()前设置,设晚了无效 - 非缓冲下不能调用
rowCount()、不能seek()、不能二次遍历结果集——这是换内存省出来的代价 - MySQL 侧连接会一直保持打开,直到你 fetch 完或主动
$stmt = null;
fetch 过程中变量没 unset,引用链卡住 GC
PHP7.3 的垃圾回收对循环引用支持有限,尤其在 CLI 长时脚本里。你 fetch 一行、处理一行、但没显式切断变量引用,上一行的数据对象可能因为闭包、静态数组、全局缓存等被意外持有。
比如:foreach ($stmt as $row) { process($row); } 看似干净,但若 process() 内部把 $row 写进了 static $cache[] 或传给了某个长期存活对象,那整行数据就再也收不回来了。
立即学习“PHP免费学习笔记(深入)”;
- 处理完单行后立刻
unset($row)或赋值为null - 避免在循环内创建闭包并
use ($row),除非你确定生命周期可控 - 每处理 N 行(如 1000 行)后手动触发
gc_collect_cycles(),PHP7.3 默认 GC 周期较长,不主动调可能拖到脚本结束才扫 - 用
xdebug_debug_zval('row')检查引用计数,确认是否真被释放
sleep() 轮询时不清理,内存“假性泄漏”
很多后台任务写成 while (true) { $rows = $pdo->query(...)->fetchAll(); process($rows); sleep(30); }——这等于每 30 秒往内存里堆一份新数据,旧的却赖着不走。因为 sleep() 不销毁作用域,局部变量全挂着,PDO Statement 对象、结果数组、临时字符串一个不少。
更糟的是,如果用了 PDO::FETCH_CLASS,每个对象还可能带 private 属性引用大数组,GC 根本找不到断点。
- 每次循环末尾必须
unset($rows)、$stmt = null;、$pdo = null;(如果连接是复用的,至少断开引用) - 不要在循环里反复
new PDO(),连接对象本身也吃内存;改用连接池或短生命周期 PDO 实例 - 终极方案:别用
sleep()长驻,改用系统 cron 每 30 秒拉起一次新进程——PHP 生命周期天然清内存
MySQL 连接未 close,底层 socket 和 buffer 滞留
PDO 对象销毁时通常会自动 close,但前提是它没被其他变量引用。一旦你把它塞进全局数组、静态属性、或作为参数传进某个长期对象,连接资源就锁死了。PHP7.3 不会强制回收这类资源,mysqlnd 底层的 socket 缓冲区和结果集内存块持续占着。
现象是:memory_get_usage() 稳定不降,但 ps aux | grep php 看 RSS 却缓慢爬升,lsof -p [pid] 显示大量 socket 处于 ESTABLISHED 状态。
- 明确调用
$pdo->closeCursor()(释放当前查询的游标) +$pdo = null; - 如果是 MySQLi,必须
$mysqli->close();,仅$mysqli = null;不够 - 检查是否用了
PDO::ATTR_PERSISTENT => true,持久连接在 CLI 下极易积累,应禁用 - 用
SHOW PROCESSLIST对比连接数和你的脚本并发量,若连接数只增不减,基本就是没正确 close
unset,而是多个小疏忽叠在一起:缓冲查询 + 循环引用 + 持久连接 + sleep 驻留——它们互相掩盖,让内存像温水煮青蛙一样慢慢涨上去。动手前先用 memory_get_usage(true)(获取真实分配量)在关键节点打点,比猜快得多。



















