不能用$this->db->get()->result()导出大数据,因其会将全部联查结果(如50万行×2kb=1gb)一次性加载至内存,远超php默认128mb限制,且result_array()额外复制索引数组加剧溢出;必须改用临时表+主键分段的分块流式处理。

直接用 $this->db->get()->result() 加载几万行联查结果,必然触发 Allowed memory size exhausted。这不是配置问题,是数据获取方式错了——必须放弃「一次性全量加载」,改用分块流式处理。
为什么不能用 $this->db->get()->result() 导出大数据?
CodeIgniter 的 result() 会把整张查询结果集(含所有 JOIN 后的冗余字段)全部读入 PHP 内存,哪怕你只想要写 CSV。比如三表联查后生成 50 万行记录,每行平均 2KB,光数据就占 1GB 内存,远超默认 128MB 限制。更糟的是,result_array() 还额外复制一份索引数组,进一步放大开销。
常见错误现象:
- 导出中途报错:
Fatal error: Allowed memory size of 134217728 bytes exhausted - 页面空白,日志里只有
PHP Fatal error,没堆栈 - 提升
memory_limit暂时有效,但上线后仍可能因并发导出崩掉
用临时表 + 主键分段实现分块查询
CodeIgniter 原生不支持 MySQL 的 mysqli_use_result 流式游标,但可以用「建临时表 + 按主键范围分页」模拟等效效果。核心是复用原有 SQL,只加一层包装。
实操要点:
- 先执行原始 SQL 创建临时表:
CREATE TEMPORARY TABLE tmp_export AS SELECT p.*, pl.*, t.* FROM tblProgram p JOIN tblPlots pl ON ... - 确保临时表有主键或唯一递增字段(如
pkProgramID),否则无法安全分段 - 用
SELECT * FROM tmp_export WHERE pkProgramID > ? ORDER BY pkProgramID LIMIT 2000循环取 chunk - 每次只 fetch 2000 行,处理完立即
fputcsv()写入文件流,不保留结果集引用
注意:临时表在连接关闭时自动销毁,无需手动 DROP;但若使用持久连接,需显式清理。
CSV 导出时避免内存泄漏的关键细节
即使分块,fopen() 和 fputcsv() 用法不对,照样吃光内存。最易被忽略的三点:
- 不要用
write_file()累积拼接整个 CSV 字符串——它会把全部内容存成 PHP 字符串变量 - 打开文件必须用
'w'模式,不是'a';且首次写入前要调用fputcsv($fp, $header)写表头 - 每个 chunk 处理完后,显式调用
unset($chunk),尤其当 chunk 是对象数组时(CodeIgniter 默认返回对象) - 如果导出接口是 HTTP 请求,记得设置
set_time_limit(0),防止超时中断
什么时候该考虑换方案?
临时表方案适用于大多数后台导出场景,但遇到以下情况,就得换思路:
- 数据库权限不允许
CREATE TEMPORARY TABLE(如某些云数据库只读账号)→ 改用子查询 +OFFSET/LIMIT,但要注意深度分页性能衰减 - 需要实时导出、且数据变更频繁 → 临时表可能滞后,应加时间戳条件过滤,如
WHERE created_at > DATE_SUB(NOW(), INTERVAL 1 HOUR) - 单次导出超过 500 万行 → 即使分块,HTTP 连接也容易断,建议转为异步任务 + 下载链接通知
真正卡住人的,往往不是语法,而是没意识到「导出」本质是 I/O 流程,不是数据计算——重点不在怎么查,而在怎么不让数据在内存里堆积。











