PHP使用PDO查询MySQL后结果集内存无法释放问题求助
问题原因
- 你调用
memory_get_usage(true)返回的是PHP向操作系统申请的总内存块大小,不是实际在用的内存。PHP为了减少频繁向OS申请/释放内存的开销,会把已经申请到的空闲内存留在进程内复用,不会主动还给操作系统,所以你看到的总内存不会下降,但是这部分空闲内存是可以给后续脚本逻辑使用的,不属于内存泄漏。 - 禁用模拟预处理(
PDO::ATTR_EMULATE_PREPARES => false)时,MySQL原生查询的结果集会默认全部缓冲到客户端内存,就算你unset了变量,底层mysqlnd驱动的缓冲内存释放逻辑也不会立刻把内存还给OS,只会标记为进程内可用。 fetchAll(PDO::FETCH_OBJ)会一次性把所有结果加载到PHP内存,大数据量下本来就会占用很高的内存块。
验证和解决方法
验证是否真的内存泄漏
可以在释放变量、执行gc之后,再申请一块大内存,看memory_get_usage(true)会不会继续暴涨:
// 原有释放逻辑之后 gc_collect_cycles(); echo 'Memory usage after gc: ' . round(memory_get_usage(true) / (1024 * 1024), 3) . ' MB' . "\n"; // 再申请一个和之前结果集差不多大的数组 $test = array_fill(0, 100000, new stdClass()); echo 'Memory usage after alloc new data: ' . round(memory_get_usage(true) / (1024 * 1024), 3) . ' MB' . "\n";
如果总内存没有涨到64MB左右,而是基本维持在32MB,就说明之前的内存已经被复用了,没有泄漏。
彻底降低峰值内存、让内存尽早释放的方案
- 改用逐行读取结果代替
fetchAll,处理一行释放一行的内存,峰值内存会大幅降低:
$stmt = $pdo->query('SELECT * FROM `SOME_LARGE_TABLE`;'); while ($row = $stmt->fetch(PDO::FETCH_OBJ)) { // 处理单条数据 // 处理完无需保留$row的话直接unset($row)即可 } $stmt->closeCursor(); // 显式关闭游标,清空底层结果集缓冲
- 显式调用
PDOStatement::closeCursor(),在处理完结果集之后立刻执行,会主动清空mysqlnd驱动层的结果集缓冲,比只unset变量生效更快。 - 如果确实需要一次性拿到所有结果,可以开启PDO的非缓冲查询,适合超大结果集场景:
$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false); $stmt = $pdo->query('SELECT * FROM `SOME_LARGE_TABLE`;'); // 逐行读取即可,结果不会全部缓冲到本地
注意:非缓冲查询在结果集读取完成前,不能发起新的查询,否则会报错。
- 如果需要强制让PHP尽可能把空闲内存还给操作系统,可以在PHP 7.3+版本调用
gc_mem_caches()函数,在gc_collect_cycles()之后执行,会清理PHP的内存缓存池,把空闲内存还给OS:
gc_collect_cycles(); gc_mem_caches(); // 新增这行 echo 'Memory usage at end: ' . round(memory_get_usage(true) / (1024 * 1024), 3) . ' MB' . "\n";
内容的提问来源于stack exchange,提问作者simul8ntheory
相关产品推荐
相关产品推荐

