本文目录导读:

在 PHP 中处理冷数据(不常访问的历史数据)导出,核心目标是降低内存占用和避免超时,以下是几种主流且高效的方案,按推荐程度排序:
使用生成器 + 数据库游标(最推荐)
这是 PHP 处理百万级数据导出的标准做法,通过 yield 逐行产出数据,配合数据库的 unbuffered query,内存占用恒定在几十 KB 左右。
<?php
/**
* 流式导出冷数据到 CSV
* 适用场景:数据量巨大(>10万行),普通服务器
*/
// 1. 核心:生成器函数,逐行从数据库拉取(不会一次性加载到内存)
function yieldRows(PDO $pdo, string $sql): Generator {
// 关键:使用 PDO::MYSQL_ATTR_USE_BUFFERED_QUERY = false (MySQL)
// 或 PostgreSQL 的 cursor(事务内使用)
$stmt = $pdo->prepare($sql);
$stmt->execute();
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
yield $row; // 生成一行,用完即丢
}
$stmt->closeCursor();
}
// 2. 导出到文件(避免直接输出到浏览器导致内存炸)
function exportColdData(string $filePath, string $tableName): void {
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8mb4', 'user', 'pass', [
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false, // 关键配置:不缓冲查询结果
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
// 打开文件句柄(写入模式)
$fp = fopen($filePath, 'w');
fputcsv($fp, ['ID', '姓名', '创建时间']); // 表头
// 使用生成器逐行遍历
$gen = yieldRows($pdo, "SELECT id, name, created_at FROM {$tableName}");
foreach ($gen as $row) {
// 写入 CSV(每写一行都立即刷到磁盘)
fputcsv($fp, $row);
}
fclose($fp);
echo "导出完成,文件大小: " . round(filesize($filePath) / 1024 / 1024, 2) . " MB";
}
// 调用
exportColdData('/tmp/cold_data_2023.csv', 'orders_archive_2023');
?>
优点:
- 内存占用恒定(无论数据量多大)
- 不会触发 PHP
memory_limit限制 - 文件直接写磁盘,不占 PHP 进程内存
数据库差异:
- MySQL:必须在 PDO 连接时设置
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false,且一次只能有一个活跃的 unbuffered 查询。 - PostgreSQL:需要开启事务并使用游标(
DECLARE cursor),或直接使用COPY TO命令(最快)。 - SQLite:可以使用
sqlite3的unbuffered模式。
MySQL 原生导出(最高性能)
如果数据源是 MySQL/MariaDB,甚至可以不用 PHP 处理数据,直接调用 MySQL 的 SELECT ... INTO OUTFILE 导出服务器本地文件,PHP 只负责触发命令和提示下载。
<?php
// 使用 MySQL 原生导出(速度极快,几乎不占 PHP 内存)
$pdo = new PDO('mysql:host=localhost;dbname=archive', 'root', 'pass');
$pdo->exec("
SELECT id, name, created_at
INTO OUTFILE '/tmp/orders_export.csv'
FIELDS TERMINATED BY ','
OPTIONALLY ENCLOSED BY '\"'
LINES TERMINATED BY '\n'
FROM orders_archive_2023
WHERE created_at < '2022-01-01'
");
// PHP 把 /tmp/orders_export.csv 通过 HTTP 输出给用户
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename="export.csv"');
readfile('/tmp/orders_export.csv'); // readfile 是流式读取,不占内存
// 或者直接移动文件到下载目录
rename('/tmp/orders_export.csv', '/var/www/downloads/export_2023.csv');
?>
关键限制:
- MySQL 的
secure_file_priv变量限制了可以写入的目录(默认可能是 NULL,需要SHOW VARIABLES LIKE 'secure_file_priv';查看)。 - 文件只能写到 MySQL 服务器所在的服务器上(如果是远程数据库,文件不在你的 Web 服务器上)。
- 对 SQL 语法要求较高。
使用扩展库或工具(辅助)
如果不想手动处理游标,可以使用这些封装好的库:
-
league/csv:处理 CSV 写入,支持fputcsv的流式操作,内置Writer对象,可以配合生成器使用。 -
box/spout:一个 PHP 库,用于流式读取/写入 CSV、Excel (ODS/XLSX),特点是支持大文件,不需要memory_limit。use Box\Spout\Writer\Common\Creator\WriterEntityFactory; $writer = WriterEntityFactory::createCSVWriter(); $writer->openToFile('/tmp/output.csv'); // 同样是流式处理,内部处理大文件 foreach ($dataGenerator as $row) { $writer->addRow(WriterEntityFactory::createRowFromArray($row)); } $writer->close();
优化 PHP 配置 + 分批 + 后台任务
如果上述方案都不适用(例如数据库不支持游标),可以退而求其次:
- 使用
LIMIT分批查询(避免OFFSET深度翻页性能问题,用游标条件代替)。 - 结合 HTTP 404 重定向:让用户点击 3 次,每次导出 1/3 的数据。
- 将导出任务放入 Redis 队列,由 CLI 脚本(
php cli.php)在后台执行,生成完文件后用户再下载。
// 分批查询的伪代码
$lastId = 0;
$fp = fopen('/tmp/export.csv', 'w');
do {
$rows = $pdo->query("SELECT * FROM archive WHERE id > {$lastId} ORDER BY id LIMIT 10000")->fetchAll();
if (empty($rows)) break;
foreach ($rows as $row) {
fputcsv($fp, $row);
$lastId = $row['id']; // 关键:用 id 代替 OFFSET
}
// 如果内存仍然高,可加 unset($rows);
$continue = count($rows) == 10000;
} while ($continue);
fclose($fp);
| 场景 | 推荐方案 | 内存占用 |
|---|---|---|
| 有 MySQL,且数据量百万级 | MySQL INTO OUTFILE |
极低 |
| 有 MySQL,但远程或需要实时过滤 | PDO unbuffered + 生成器 |
恒定 |
| PostgreSQL 数据库 | COPY TO 或 事务+游标 |
极低 |
| 数据在文件里(CSV/JSON)需要转换 | SplFileObject 流式读取 |
低 |
| 必须使用共享主机(无法配置 PDO 选项) | LIMIT + 游标轮询 |
可控制 |
关键注意事项:
- 设置脚本超时:
set_time_limit(0);或ignore_user_abort(true); - 输出缓冲:
while (ob_get_level()) ob_end_clean();清掉之前的输出缓冲。 - 数据库连接:使用
PDO或mysqli的非缓冲查询时必须确保没有其他活跃查询。 - 最终文件下载:用
readfile()代替file_get_contents(),避免第二次占用内存。 - 文件锁定:如果导出文件是生成在 Web 目录,注意加
.htaccess或权限,防止别人直接访问。
选择方案时,优先考虑方案一(生成器+游标),因为它最通用且可控,若数据库权限允许则直接使用方案二(原生导出)达到性能极致。
版权声明: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直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播