You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)

核心原因

  1. file_get_contents会将整个文件一次性加载到PHP内存,大文件直接占用大量内存;
  2. 额外执行base64_encode会让内存占用再增加约33%(Base64编码将每3字节转为4字节),双重内存消耗直接触发溢出;
  3. 即使设置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内存:

  1. 修改MySQL配置(my.cnf或my.ini):local_infile=1,重启MySQL服务;
  2. PHP连接数据库时开启LOCAL_INFILE选项:
$pdo = new PDO(
    'mysql:host=你的主机地址;dbname=你的数据库名',
    '用户名',
    '密码',
    [PDO::MYSQL_ATTR_LOCAL_INFILE => true]
);
  1. 执行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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 23:47:45