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

Laravel(PHP/MySQL)存储500MB大文件遇内存不足,求流/分块存储方案

解决大文件(500MB)存入MySQL的内存耗尽问题(Laravel/通用PHP场景)

一、PHP到MySQL的数据流存储方法

核心思路是不一次性把文件加载到内存,用PHP流资源配合MySQL参数绑定实现流式写入,避开file_get_contents的内存瓶颈。

Laravel实现

利用Laravel数据库连接获取底层PDO实例,绑定文件流执行插入:

$filePath = '/path/to/your/large.pdf';
$fileHandle = fopen($filePath, 'rb');

// 获取PDO连接
$pdo = DB::connection()->getPdo();

// 假设表名为`large_files`,`file_data`为LONGBLOB类型
$stmt = $pdo->prepare("INSERT INTO large_files (file_data) VALUES (:file)");

// 绑定流资源,指定LOB类型
$stmt->bindParam(':file', $fileHandle, PDO::PARAM_LOB);

$stmt->execute();
fclose($fileHandle);

这种方式让PHP与MySQL以流形式传输数据,内存仅占用流缓冲区大小(默认几KB),不会触发内存耗尽。

通用PHP实现

不用Laravel时直接用PDO逻辑:

$dsn = 'mysql:host=localhost;dbname=your_db;charset=utf8';
$pdo = new PDO($dsn, 'user', 'pass');

$fileHandle = fopen('/path/to/your/large.pdf', 'rb');
$stmt = $pdo->prepare("INSERT INTO large_files (file_data) VALUES (:file)");
$stmt->bindParam(':file', $fileHandle, PDO::PARAM_LOB);
$stmt->execute();

fclose($fileHandle);

二、分块存储方法

受max_allowed_packet(15MB)限制时,可将文件拆分为多个不超过14MB的块,分表存储:

1. 数据库表设计

-- 文件元数据表:存基本信息
CREATE TABLE file_metadata (
    id INT AUTO_INCREMENT PRIMARY KEY,
    filename VARCHAR(255) NOT NULL,
    total_chunks INT NOT NULL,
    file_size BIGINT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 文件分块表:存拆分后的块数据
CREATE TABLE file_chunks (
    id INT AUTO_INCREMENT PRIMARY KEY,
    file_id INT NOT NULL,
    chunk_index INT NOT NULL,
    chunk_data LONGBLOB NOT NULL,
    FOREIGN KEY (file_id) REFERENCES file_metadata(id)
);

2. PHP分块写入实现

$filePath = '/path/to/your/large.pdf';
$chunkSize = 14 * 1024 * 1024; // 14MB
$fileSize = filesize($filePath);
$totalChunks = ceil($fileSize / $chunkSize);

// 插入元数据并获取文件ID
$fileId = DB::table('file_metadata')->insertGetId([
    'filename' => basename($filePath),
    'total_chunks' => $totalChunks,
    'file_size' => $fileSize
]);

$fileHandle = fopen($filePath, 'rb');
$chunkIndex = 0;

while (!feof($fileHandle)) {
    $chunkData = fread($fileHandle, $chunkSize);
    DB::table('file_chunks')->insert([
        'file_id' => $fileId,
        'chunk_index' => $chunkIndex,
        'chunk_data' => $chunkData
    ]);
    $chunkIndex++;
}

fclose($fileHandle);

三、从MySQL向用户流输出数据方案

通过分块读取+逐块输出实现流式下载,避免一次性加载大文件到内存。

1. 单字段LONGBLOB流式输出

利用SUBSTRING分块读取:

$fileId = 1; // 目标文件ID
$chunkSize = 1 * 1024 * 1024; // 每次读1MB

// 获取文件元信息
$fileInfo = DB::table('large_files')->where('id', $fileId)->first();

// 设置下载响应头
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="' . $fileInfo->filename . '"');
header('Content-Length: ' . $fileInfo->file_size);
header('Cache-Control: no-cache');

$offset = 0;
while ($offset < $fileInfo->file_size) {
    // MySQL的SUBSTRING从1开始计数
    $chunk = DB::table('large_files')
        ->where('id', $fileId)
        ->value(DB::raw("SUBSTRING(file_data, {$offset}+1, {$chunkSize})"));
    
    echo $chunk;
    ob_flush();
    flush();
    
    $offset += $chunkSize;
}
exit;

2. 分块存储流式输出

按块顺序读取输出:

$fileId = 1;
$metadata = DB::table('file_metadata')->where('id', $fileId)->first();

// 设置响应头
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="' . $metadata->filename . '"');
header('Content-Length: ' . $metadata->file_size);
header('Cache-Control: no-cache');

// 按块顺序读取并输出
$chunks = DB::table('file_chunks')
    ->where('file_id', $fileId)
    ->orderBy('chunk_index')
    ->get();

foreach ($chunks as $chunk) {
    echo $chunk->chunk_data;
    ob_flush();
    flush();
}
exit;

注意事项

  • 确保字段类型为LONGBLOB(最大支持4GB),不要用BLOB/MEDIUMBLOB(容量不足)。
  • 输出时主动调用ob_flush()和flush(),避免缓冲区累积占用内存。
  • 分块存储时,写入和读取必须保证块顺序正确,否则文件损坏。

内容的提问来源于stack exchange,提问作者mankowitz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 00:42:46