如何通过PHP从MySQL读取超大Blob字段并生成可下载文件
解决方案
方案1:修复PHP PDO LOB流式读取方案
你之前的方案问题在于没有正确使用PDO返回的LOB流资源,直接将其作为字符串写入导致截断,同时需同时配置缓冲区大小和无缓冲查询参数,还要调整PHP运行时限制避免超时或内存不足。
前置配置调整
- 调整PHP运行时参数:
memory_limit设置为大于最大文件大小或-1,max_execution_time设置为0取消超时限制 - PDO配置中同时加入
MYSQL_ATTR_MAX_BUFFER_SIZE和MYSQL_ATTR_USE_BUFFERED_QUERY参数
完整可运行代码
// 运行时调整限制 ini_set('memory_limit', '-1'); set_time_limit(0); $dsn = "mysql:host=$host;dbname=$db;charset=$charset"; $options = [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => false, PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false, // 缓冲区大小设置为超过你存储的最大文件,示例为800MB PDO::MYSQL_ATTR_MAX_BUFFER_SIZE => 1024*1024*800 ]; try { $pdo = new PDO($dsn, $user, $pass, $options); } catch (\PDOException $e) { throw new \PDOException($e->getMessage(), (int)$e->getCode()); } $stmt = $pdo->prepare('SELECT a.TITLE, a.ATTVERSION, a.ATTACHMENTID, a.CONTENTTYPE, a.FILESIZE, d.DATA FROM ATTACHMENTS a LEFT JOIN ATTACHMENTDATA d ON d.ATTACHMENTID=a.ATTACHMENTID WHERE a.ATTACHMENTID= ?'); $stmt->execute([$fileid]); $stmt->bindColumn(1, $title, PDO::PARAM_STR, 256 ); $stmt->bindColumn(2, $attversion, PDO::PARAM_INT); $stmt->bindColumn(3, $attid, PDO::PARAM_INT); $stmt->bindColumn(4, $contenttype, PDO::PARAM_STR, 256); $stmt->bindColumn(5, $filesize, PDO::PARAM_INT); // 绑定BLOB字段为流资源 $stmt->bindColumn(6, $data, PDO::PARAM_LOB); $stmt->fetch(PDO::FETCH_BOUND); // 流式写入本地文件,无需加载整个文件到内存 $outputPath = $storage_dir . "/" . $filename; $outStream = fopen($outputPath, 'wb'); // 每次读取8KB写入,内存占用极低 stream_copy_to_stream($data, $outStream, 8192); fclose($outStream); fclose($data);
方案2:调用Bash脚本导出(更高稳定性,适合大文件场景)
直接调用MySQL客户端导出BLOB内容,完全绕过PHP运行时限制,性能和稳定性都优于纯PHP实现。
第一步:编写导出脚本export_blob.sh
#!/bin/bash # 入参:$1=附件ID $2=输出文件完整路径 MYSQL_HOST="你的MySQL服务器地址" MYSQL_USER="数据库用户名" MYSQL_PASS="数据库密码" MYSQL_DB="数据库名" ATTACH_ID=$1 OUTPUT_PATH=$2 # --raw参数不转义特殊字符,--quick强制逐行输出避免客户端缓冲区溢出 mysql -h "$MYSQL_HOST" -u "$MYSQL_USER" -p"$MYSQL_PASS" "$MYSQL_DB" --raw --quick -N -e "SELECT d.DATA FROM ATTACHMENTDATA d WHERE d.ATTACHMENTID = $ATTACH_ID;" > "$OUTPUT_PATH"
给脚本设置权限:chmod 700 export_blob.sh,避免其他用户读取密码。
第二步:PHP调用脚本
$attId = (int)$fileid; $outputPath = $storage_dir . "/" . $filename; // 参数转义避免命令注入 $safeAttId = escapeshellarg($attId); $safeOutputPath = escapeshellarg($outputPath); // 执行导出 exec("/path/to/export_blob.sh $safeAttId $safeOutputPath", $output, $returnVar); if ($returnVar !== 0) { throw new Exception("文件导出失败,错误码:$returnVar"); }
方案优势
- 不受PHP内存、超时限制,支持GB级超大文件导出
- 导出速度比纯PHP实现快30%以上,适合批量导出场景
- 无需修改PHP配置,对运行环境兼容性更高
内容的提问来源于stack exchange,提问作者Photovor
相关产品推荐
相关产品推荐

