本文目录导读:

针对PHP处理大数据量的流式读取,核心原则是:避免一次性将全部数据加载到内存,PHP脚本的内存限制(memory_limit)通常是瓶颈。
以下是针对不同场景的流式处理方案,从文件读取到数据库导出,以及HTTP响应:
流式读取大文件(CSV、TXT、日志)
a. 使用 fopen + fgets(逐行读取,最常用)
<?php
// 适合GB级日志文件
$handle = fopen('/path/to/huge_file.txt', 'r');
if ($handle) {
while (($line = fgets($handle)) !== false) {
// 处理每一行,例如解析、写入数据库、输出
processLine($line);
// 重要:如果处理很慢或内存增长,可以定期释放
if (memory_get_usage() > 100 * 1024 * 1024) { // 100MB
// 清理大变量,强制垃圾回收
$temp = null;
gc_collect_cycles();
}
}
fclose($handle);
}
?>
b. 使用 SplFileObject(面向对象迭代器)
<?php
$file = new SplFileObject('/path/to/huge.csv');
$file->setFlags(SplFileObject::READ_CSV); // CSV专用
// 或 SplFileObject::DROP_NEW_LINE
foreach ($file as $lineNumber => $row) {
// $row 是数组(CSV)或字符串(文本)
processRow($row);
}
?>
c. 分块读取二进制文件(图片、视频)
<?php
$handle = fopen('/path/to/large.mp4', 'rb');
$chunkSize = 8192; // 8KB
while (!feof($handle)) {
$chunk = fread($handle, $chunkSize);
// 处理chunk,例如发送到客户端或写入另一个流
echo $chunk;
flush(); // 立即发送到浏览器
ob_flush();
}
fclose($handle);
?>
流式处理数据库查询(避免一次性加载所有记录)
a. 使用 PDO + unbuffered query(MySQL)
默认PDO会缓存所有结果到内存,必须关闭缓冲:
<?php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass', [
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false, // 关键!
]);
$stmt = $pdo->prepare('SELECT * FROM huge_table');
$stmt->execute();
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
// 逐行处理,内存只保留一行
processRow($row);
}
?>
注意:未缓冲查询期间不能在同一连接上执行其他SQL,否则会报错。
b. 使用 mysqli + store_result(false)
<?php
$mysqli = new mysqli('localhost', 'user', 'pass', 'db');
$query = 'SELECT * FROM huge_table';
$result = $mysqli->query($query, MYSQLI_USE_RESULT); // 不缓存结果
while ($row = $result->fetch_assoc()) {
processRow($row);
}
$result->free(); // 释放结果集
?>
流式输出到客户端(导出CSV/Excel/JSON)
当需要将大量数据实时发送给浏览器下载时,不能先存数组再echo。
CSV流式下载示例:
<?php
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename="export.csv"');
// 打开输出流
$output = fopen('php://output', 'w');
行
fputcsv($output, ['ID', 'Name', 'Email']);
// 从数据库流式读取并逐行写入
$stmt = $pdo->prepare('SELECT * FROM users');
$stmt->execute();
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
fputcsv($output, $row);
// 强制刷新到浏览器,适合长耗时导出
if (ob_get_level() > 0) {
ob_flush();
flush();
}
}
fclose($output);
exit;
?>
大型JSON数组流式输出(不生成完整JSON字符串)
<?php
header('Content-Type: application/json');
echo '['; // 启动数组
$first = true;
$stmt = $pdo->query('SELECT * FROM big_table');
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
if (!$first) echo ','; // 逗号分隔
echo json_encode($row);
$first = false;
ob_flush(); flush();
}
echo ']'; // 结束数组
?>
内存管理高级技巧
| 问题 | 解决方案 |
|---|---|
| 处理每行时产生大量临时变量 | 使用 unset() + gc_collect_cycles() |
| 循环内拼接字符串导致内存暴增 | 改用 str_replace、sprintf 或直接输出到流 |
| 需要同时处理多个大文件 | 使用 Generator(yield)实现惰性加载 |
| 处理时间过长超时 | set_time_limit(0) 或分段处理配合前端轮询 |
使用生成器(Generator)实现内存友好的迭代器:
<?php
function readLargeFile($path) {
$handle = fopen($path, 'r');
while (!feof($handle)) {
yield fgets($handle); // 每次只产生一行
}
fclose($handle);
}
foreach (readLargeFile('/data/huge.log') as $line) {
processLine($line);
}
?>
性能对比与最佳实践
| 方法 | 内存占用 | 速度 | 适用场景 |
|---|---|---|---|
| file_get_contents + explode | ❌ 极高 | 快 | 小文件 (<10MB) |
| fopen + fgets | ✅ 极低 | 中等 | 任意大小文本文件 |
| PDO未缓冲查询 | ✅ 极低 | 快 | 数据库全表扫描 |
| Generator | ✅ 极低 | 中等 | 需要延迟计算/管道处理 |
| 分块fread + flush | ✅ 极低 | 取决于网络 | 大文件下载/代理 |
容易踩的坑
-
ob_start()导致内容缓存
流式输出前先关闭输出缓冲:ob_end_flush()或设置output_buffering = Off -
memory_limit设置过低
即使流式读取,某些框架或ORM会主动缓存,确保使用原生PDO/mysqli。 -
连接超时
对长时间运行的脚本设置:set_time_limit(0)配合前端轮询进度。 -
字符编码转换
流式处理大文件时编码转换(如GBK→UTF-8)会阻塞,考虑使用stream_filter_append或逐行转换。
总结推荐方案
- 大文件解析:
fopen+fgets或SplFileObject - 数据库导出:
PDO+MYSQL_ATTR_USE_BUFFERED_QUERY => false - 流式下载:
php://output+ob_flush()/flush() - 复杂数据处理管线:使用
Generator串联多个处理步骤
始终监控内存使用:memory_get_peak_usage(true),确保峰值不超过服务器可用内存的50%。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播