直接 exportcsv() 内存爆掉是因为 db::select() 一次性加载全部数据到内存,应改用 cursor() 或原生 pdostatement 实现流式导出,逐行写入并及时 flush,同时禁用缓存、输出缓冲和避免 orm 中间层。

为什么直接 exportCsv() 会内存爆掉
ThinkPHP 默认用 Db::select() 一次性捞出全部数据再写入 CSV,10 万行 × 每行 1KB 就是 100MB 内存起步。PHP 进程撑不住,直接报 Fatal error: Allowed memory size exhausted,不是代码写错了,是设计没走流式路径。
关键不在“导出”,而在“不把全量结果 load 进 PHP 数组”。得让数据库查一条、PHP 写一行、立刻 flush 到浏览器,中间不缓存。
- 必须关闭查询缓存:
->cache(false),否则底层 PDO 仍可能预取全部结果 - 不能用
Db::select()或$query->select(),它们返回数组;要用$query->cursor()(TP6.1+)或原生 PDOStatement(TP6.0) - 响应头要提前设好,且禁用输出缓冲:
ob_end_clean()+flush(),不然 Nginx/Apache 可能攒着不发
TP6.1+ 用 cursor() 实现真流式
cursor() 返回的是可遍历的游标对象,底层调用 PDO::FETCH_CURSOR,按需从 MySQL 拉数据,内存占用稳定在几百 KB 级别。
从 CSV 数据集生成图数据库加载器和三元组映射。将表格 CSV 数据转换为图数据库可直接使用的节点、边和三元组。
use think\facade\Db;
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename="data.csv"');
header('Cache-Control: no-cache');
$output = fopen('php://output', 'w');
fputcsv($output, ['id', 'name', 'created_at']); // 表头
$query = Db::name('user')->field('id,name,created_at')->where('status', 1);
foreach ($query->cursor() as $row) {
fputcsv($output, $row);
}
fclose($output);
-
cursor()不支持limit()和page(),它依赖数据库游标机制,分页得靠 SQL 自身(如WHERE id > ? ORDER BY id LIMIT 1000) - 字段名必须显式指定
field(),否则*可能触发额外元数据查询,影响流式稳定性 - 若表有 blob/text 字段,确保它们不会被自动转成字符串塞进内存——加
->withoutField('content')过滤掉
TP6.0 或需要兼容老版本?改用原生 PDOStatement
TP6.0 没 cursor(),但你可以绕过 Query 构建器,直连 PDO 获取 statement,手动 fetch:
$pdo = Db::connect()->getPdo();
$sql = "SELECT id, name, created_at FROM `user` WHERE status = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([1]);
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename="data.csv"');
ob_end_clean();
$output = fopen('php://output', 'w');
fputcsv($output, ['id', 'name', 'created_at']);
while ($row = $stmt->fetch(\PDO::FETCH_NUM)) {
fputcsv($output, $row);
}
fclose($output);
- 注意:
fetch(\PDO::FETCH_NUM)比FETCH_ASSOC快且省内存,CSV 只需顺序值 - 别在循环里调用
Db::table()或任何 ORM 方法,它们会悄悄加载模型、触发事件、做类型转换,破坏流式 - 如果 SQL 里用了子查询或 JOIN,确认 MySQL 的
net_buffer_length足够(默认 1MB),否则大字段可能卡住游标
中文乱码和 Excel 打不开?两个硬坑必须填
浏览器下载 CSV 后用 Excel 打开显示乱码,不是编码问题,是 BOM 缺失;Excel 直接双击打不开,则是 header 或换行符不规范。
- UTF-8 CSV 必须加 BOM:在
fopen('php://output', 'w')后立刻写fwrite($output, "\xEF\xBB\xBF"); - 表头字段含逗号、换行、引号时,
fputcsv()会自动加引号,但前提是 PHP 版本 ≥7.4 —— 低版本要自己str_replace转义,不如升级 - 不要用
echo或print输出任何额外字符(包括空格、换行),哪怕控制器末尾多一个空行,Excel 都会报“文件格式错误” - Nginx 下记得关
fastcgi_buffering off;,否则它会攒满 4KB 才吐给浏览器,导致“下载卡住”假象
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!










