fgetcsv一次性读完再导入会超时,因php默认max_execution_time为30秒,500+行csv易触发超时错误;且阻塞执行、无法回滚、日志难定位。

为什么 fgetcsv 一次性读完再导入会超时
PHP 默认的 max_execution_time 通常为 30 秒,而班级通信录 CSV 文件若含 500+ 行、字段多(如含头像路径、备注等),用 fgetcsv 全量读入内存 + 循环 INSERT,很容易在第 200 行左右触发 Fatal error: Maximum execution time of 30 seconds exceeded。更关键的是:这不仅是“慢”,而是阻塞式执行 —— 中间出错无法回滚,日志难定位,用户看到白屏。
用 set_time_limit(0) 不是万能解法
强行延长超时看似简单,但实际埋雷:
- Web 服务器(如 Nginx)有独立的
fastcgi_read_timeout,PHP 层面设了0,Nginx 可能在 60 秒后直接切断连接,返回 504 Gateway Timeout - 共享主机或云函数环境根本不允许调用
set_time_limit,会报Warning: set_time_limit(): Cannot set max execution time limit - 大文件持续占用 PHP 进程,导致并发能力骤降,其他请求排队卡死
分批导入的核心实操要点
真正稳定的方案是「客户端分片 + 服务端流式处理」,不是靠 PHP 硬扛:
- 前端用
FileReader分块读取 CSV(例如每次 50 行),通过 AJAX 分多次 POST 到接口,每次携带offset=0&limit=50和文件标识符(如upload_id) - 后端不依赖
$_FILES重读文件,而是将上传的 CSV 先存为临时文件(如/tmp/upload_abc123.csv),每次只fopen+fseek定位到指定行号开始读,用fgetcsv读limit行后立即fclose - 每批插入前加事务:
$pdo->beginTransaction(),成功则commit,失败rollback并返回错误行号,前端可续传 - 关键参数控制:
ini_set('memory_limit', '128M')防爆内存;stream_set_timeout($fp, 5)避免文件句柄卡死
示例关键片段:
立即学习“PHP免费学习笔记(深入)”;
// 每次请求只处理指定范围
$fp = fopen($tempFile, 'r');
fseek($fp, $startPos); // $startPos 来自上一批末尾 ftell()
for ($i = 0; $i < $limit && ($row = fgetcsv($fp)) !== false; $i++) {
$stmt->execute($row);
}
$endPos = ftell($fp); // 下次从这里开始
fclose($fp);
别忽略客户端状态同步和断点续传
用户刷新页面、网络中断、手机切后台都会导致上传中断。必须让前后端共同维护进度:
- 服务端记录
upload_id → {total_lines, processed_lines, status}到 Redis 或数据库,TTL 设为 2 小时 - 前端每次请求前先 GET
/api/import/status?upload_id=xxx拉取当前进度,决定从哪一行继续 - 最后一包提交后,服务端才触发完整校验(如学号重复检查)、生成导入报告,而非每批都查唯一索引拖慢速度
真正的难点不在“怎么分批”,而在“怎么让每次请求都知道自己该干啥、干完后别人还能接上” —— 状态必须显式传递,不能依赖 PHP 进程内存或 session。











