PHP执行SQL后内存不释放的主因是结果集未显式释放、连接未关闭或对象引用未切断;需调用mysqli_free_result()、unset(PDOStatement)、避免长连接残留、禁用fetchAll()而改用流式fetch()。

PHP执行SQL语句后,内存不释放的主因不是SQL本身,而是结果集未显式释放、PDO/MySQLi连接未关闭、或对象引用未切断。不处理这些,哪怕只查10行,反复执行几十次也会OOM。
mysqli_query后必须调用mysqli_free_result()
使用mysqli_query()获取结果集后,PHP不会自动释放底层结果缓冲区——尤其在mysqlnd驱动下,整张表数据可能全驻内存。不手动清理,后续查询会持续累积占用。
- 必须在遍历完
$result后立即调用mysqli_free_result($result) - 若只执行
INSERT/UPDATE/DELETE且不需要返回值,改用mysqli_real_query()+mysqli_affected_rows(),它不产生结果集,省去释放步骤 - 错误写法:
while ($row = mysqli_fetch_assoc($result)) { ... }后直接进入下一轮查询——$result仍被持有
PDOStatement需及时unset或复用
PDOStatement对象内部缓存了结果集和元信息,即使调用closeCursor(),对象本身仍占内存。对高频循环场景(如批量处理),不释放会快速堆积。
- 执行完
$stmt->fetchAll()后,立刻unset($stmt),不要依赖GC自动回收 - 避免在循环内重复
$pdo->prepare($sql)——改用一次prepare、多次execute(),否则每个PDOStatement都独立持有一份解析结构 -
closeCursor()只释放结果集,不释放预处理语句本身;真正释放需unset($stmt)或让变量超出作用域
长连接与连接池导致的隐式内存滞留
使用PDO::ATTR_PERSISTENT => true或mysqli_options(MYSQLI_OPT_CONNECT_TIMEOUT)开启长连接时,PHP进程生命周期内连接不关闭,其关联的全部查询上下文(包括上一个查询残留的结果)可能持续占用内存。
- 确认是否真需要持久连接:Web脚本通常单次请求即结束,用普通连接更安全
- 若必须用长连接,每次查询后强制
$pdo->exec('SELECT 1')或mysqli_ping()重置连接状态,防止旧结果残留 - 检查
phpinfo()中mysqli.reconnect是否为Off——设为On可在断连后自动重建,但会丢失当前连接上下文,间接清空残留
大结果集必须流式处理,禁用fetchAll()
fetchAll()把全部结果一次性载入PHP数组,是内存爆炸最常见操作。哪怕只有1万行,每行2KB,就直接吃掉20MB——而流式fetch()可控制在几百KB内。
- 永远用
while ($row = $stmt->fetch())逐行处理,而非$rows = $stmt->fetchAll() - 对导出类任务,边查边写文件:
fwrite($fp, json_encode($row)."\n"),查完即丢 - MySQLi中可用
mysqli_use_result()替代mysqli_store_result(),让数据从网络流式拉取,不缓存全量
最易被忽略的是PDOStatement对象的引用计数——哪怕你调用了closeCursor(),只要变量还存在(比如放在全局数组里),内存就不会归还。清理内存不是“做完事就完”,而是“做完事立刻斩断所有引用”。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











