PHP处理20MB以上PDF时file_get_contents内存溢出问题求助
解决大PDF文件上传至MySQL Blob内存溢出问题
问题场景
现有代码处理20MB以内文件正常,但上传20MB以上PDF时触发内存溢出,代码如下:
ini_set('memory_limit', '-1'); ini_set('post_max_size','-1'); ini_set('upload_max_filesize','-1'); ini_set('max_execution_time','0'); set_time_limit(0); $files = file_get_contents($_FILES['file']['tmp_name']); $file_base = base64_encode($files);
错误信息
Out of memory (allocated 439840768) (tried to allocate 116729376 bytes)
核心原因
file_get_contents会将整个文件一次性加载到PHP内存,大文件直接占用大量内存;- 额外执行
base64_encode会让内存占用再增加约33%(Base64编码将每3字节转为4字节),双重内存消耗直接触发溢出; - 即使设置
memory_limit=-1,也受限于服务器实际可用物理内存。
解决方案
方案1:直接用PDO绑定文件句柄写入Blob(推荐)
利用PDO的PARAM_LOB参数,直接将上传临时文件的句柄传递给MySQL,全程无需将文件内容加载到PHP内存,内存占用极低:
ini_set('post_max_size','-1'); ini_set('upload_max_filesize','-1'); ini_set('max_execution_time','0'); set_time_limit(0); // 连接数据库(根据实际配置修改) $pdo = new PDO( 'mysql:host=你的主机地址;dbname=你的数据库名;charset=utf8mb4', '用户名', '密码', [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION] ); // 准备插入语句(替换为你的表名和Blob字段名) $stmt = $pdo->prepare("INSERT INTO your_table (pdf_blob) VALUES (:pdf_data)"); // 以只读二进制模式打开上传临时文件 $file_handle = fopen($_FILES['file']['tmp_name'], 'rb'); // 绑定文件句柄到参数,指定为LOB类型 $stmt->bindParam(':pdf_data', $file_handle, PDO::PARAM_LOB); // 执行插入 $stmt->execute(); // 关闭文件句柄 fclose($file_handle);
方案2:取消Base64编码,直接写入二进制内容
如果必须通过PHP内存中转,至少取消不必要的Base64编码,减少内存占用:
ini_set('memory_limit', '-1'); ini_set('post_max_size','-1'); ini_set('upload_max_filesize','-1'); ini_set('max_execution_time','0'); set_time_limit(0); // 直接读取文件内容(仍会占用文件大小的内存,仅适合中等文件) $file_content = file_get_contents($_FILES['file']['tmp_name']); // 连接数据库并插入(示例用PDO) $pdo = new PDO('mysql:host=你的主机地址;dbname=你的数据库名', '用户名', '密码'); $stmt = $pdo->prepare("INSERT INTO your_table (pdf_blob) VALUES (?)"); $stmt->bindParam(1, $file_content, PDO::PARAM_LOB); $stmt->execute();
方案3:用MySQL LOAD DATA直接读取临时文件
开启MySQL的local_infile功能后,可直接让MySQL读取上传临时文件,完全绕开PHP内存:
- 修改MySQL配置(my.cnf或my.ini):
local_infile=1,重启MySQL服务; - PHP连接数据库时开启LOCAL_INFILE选项:
$pdo = new PDO( 'mysql:host=你的主机地址;dbname=你的数据库名', '用户名', '密码', [PDO::MYSQL_ATTR_LOCAL_INFILE => true] );
- 执行LOAD DATA语句(注意过滤文件名避免安全风险):
// 验证临时文件路径安全性(防止路径遍历) $safe_tmp_path = realpath($_FILES['file']['tmp_name']); if (strpos($safe_tmp_path, sys_get_temp_dir()) !== 0) { die("非法文件路径"); } // 执行LOAD DATA $pdo->exec(" LOAD DATA LOCAL INFILE '$safe_tmp_path' INTO TABLE your_table (pdf_blob) FIELDS TERMINATED BY '' LINES TERMINATED BY '' ");
注意事项
- Blob字段建议使用
LONGBLOB类型,支持最大4GB存储; - 务必验证上传文件的类型和大小,避免恶意文件上传;
- 方案3需确保MySQL和PHP都开启了
local_infile,且注意路径安全。
内容的提问来源于stack exchange,提问作者Nazeer Shaik
相关产品推荐
相关产品推荐

