必须同时启用非缓冲查询、doctrine iterate()和$em->clear(),否则内存优化效果减半以上;mysqli用mysqli_use_result(),pdo设pdo::mysql_attr_use_buffered_query=>false,symfony中iterate()返回原生数组流并每次循环后clear()。

用非缓冲查询逐行处理大结果集
MySQLi 和 PDO 默认使用缓冲查询,会把整个结果集一次性加载进 PHP 内存,几万行就可能吃掉 200MB+。必须显式切换为非缓冲模式,让数据库连接保持流式吐出。
- MySQLi:调用
mysqli_use_result()替代mysqli_store_result(),然后用mysqli_fetch_assoc()逐行取 - PDO:创建
PDOStatement时传入[PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false],再用foreach ($stmt as $row) - 注意:非缓冲查询期间不能执行其他语句,且连接不能复用——查完立刻
$mysqli->close()或$pdo = null
在 Symfony 中配合 Doctrine 使用 iterate() 而非 getIterator()
getIterator() 仍会把所有实体加载进 EntityManager 一级缓存,而 iterate() 返回的是原生数组流,完全绕过 ORM 层对象构建开销,内存占用可从百 MB 降到几 MB。
- 必须搭配
$em->clear()在每次迭代后调用,否则即使用了iterate(),后续查询仍会受残留实体影响 - 不要在
iterate()循环里调用$em->find()或关联加载,这会重新触发全量加载 - 示例:
foreach ($em->createQuery('SELECT u FROM User u')->iterate() as [$user]) { /* 处理 $user */ $em->clear(); }
避免在循环中累积大数组或拼接字符串
常见写法如 $rows[] = $row 或 $html .= renderRow($row) 会在内存中持续膨胀,尤其当结果集超 10k 行时极易触发 Allowed memory size exhausted。
- 改用生成器函数封装查询逻辑:
yield $row,调用方用foreach (fetchUsersAsGenerator() as $row) - 导出 CSV/JSON 等场景,直接
fwrite($fp, ...)输出到文件或响应流,不拼接完整字符串 - 若必须暂存,设硬性上限(如
if (count($batch) >= 1000) { processBatch($batch); $batch = []; })
确认 OPcache 和 Symfony 缓存未因大查询失效
大查询本身不直接影响缓存,但若查询逻辑写在控制器里、又没预热,或用了动态 SQL 拼接,会导致每次请求都重新编译模板、重建容器,间接放大内存压力。
- 检查
var/cache/prod/App_KernelProdContainer.xml修改时间:worker 模式下它启动后不应再变 - 访问
/frankenphp/phpinfo,确认opcache.hits在压测中稳定增长,opcache.memory_usage.used_memory占比 ≥ 60% - 禁用开发组件:
symfony/profiler-pack和debug-pack必须从生产 autoload 中彻底移除,它们的监听器会常驻并缓存大量调试数据
实际部署时最容易被忽略的是:非缓冲查询 + iterate() + clear() 这三者必须同时生效,缺一不可。只做其中一两项,内存下降效果会打五折以上。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











