应采用流式分块写入替代全量加载,用fopen('wb')配合fwrite()每次写入8kb并及时fflush(),避免file_put_contents()导致内存溢出。

PHP写大文件时提示Allowed memory size exhausted
这是典型内存溢出,不是磁盘空间问题。PHP默认把整个文件内容加载进内存再写入,遇到几十MB以上文件就容易崩。
关键要绕过 file_put_contents() 这类“全量加载”操作,改用流式写入。
- 别用
file_put_contents($path, $huge_string)—— 字符串先占内存,再复制进缓冲区,两倍开销 - 优先用
fopen()+fwrite()分块写,每次只处理几KB - 写完立刻
fflush()和fclose(),别等脚本结束才刷盘 - 确认
memory_limit设置合理(如256M),但别指望靠调高它解决根本问题
如何安全分块写入超大文件(比如日志或导出CSV)
核心是控制单次写入的数据量,让内存占用始终在可控范围。不是“能不能写”,而是“怎么写不爆”。
常见场景:导出10万行数据库记录、拼接GB级日志、生成压缩包内原始数据流。
立即学习“PHP免费学习笔记(深入)”;
- 用
fopen($path, 'wb')打开二进制写模式,避免换行符自动转换干扰 - 每读取/生成约8192字节(
8 * 1024)就fwrite()一次,这个值在大多数服务器上平衡了IO和内存 - 对数据库结果集,用
mysqli_stmt::fetch()或PDO::fetch(PDO::FETCH_ASSOC)逐行取,别用fetchAll() - 写入前检查
fwrite()返回值,它可能小于预期长度(比如磁盘满、权限不足),需手动处理失败
注意fopen的mode参数和系统级限制
fopen() 的第二个参数看着简单,但错一个字母就埋坑;同时系统层面对单文件大小、IO缓冲也有隐性约束。
-
'w'会清空原文件,'a'是追加——导出中途失败重试时,用'c'(打开但不截断)更安全 - Linux下单个文件超过2GB时,确保PHP编译时启用了
FILE_OFFSET_BITS=64,否则fseek()或部分fstat()可能异常 - 某些容器环境(如Docker)挂载卷有IO限速或缓冲策略,
fflush()后仍可能延迟落盘,必要时加fsync()(PHP 8.1+ 支持fflush()后调用stream_socket_shutdown()不适用,得用pcntl_fork()配合系统调用,慎用) - 如果目标路径在NFS或CIFS共享盘上,
flock()可能失效,别依赖文件锁做并发保护
临时文件写入后move失败?检查open_basedir和目录权限
很多人习惯先写到 /tmp 再 rename() 到目标位置,结果报错 Warning: rename(): Unable to rename —— 很少是PHP的问题,基本是环境配置卡住。
-
rename()跨文件系统会失败(比如/tmp是tmpfs内存盘,目标在ext4磁盘),必须用copy()+unlink() -
open_basedir如果限制了/tmp却没放行目标目录,rename()直接被拦截,错误信息却只说“Operation not permitted” - 目标目录需有PHP进程用户(如
www-data)的写权限,且父目录要有执行(x)权限才能进入——漏掉x权限时,mkdir()都会静默失败 - 用
is_writable()检查目标目录前,先clearstatcache(),避免因缓存导致判断失准
事情说清了就结束











