网上看到很多导入百万级数据到CSV文件的代码,自己都去试过,大部分都是会出现数据太大导致内存溢出,偶然间看到PHP的生成器的使用,终于找到了解决方案(所需内存很小,大概几十M足够,具体看你每次获取的数据的范围),具体我以THINKPHP5.1版本为例进行测试,具体如下:
我的环境是LNMP,PHP版本是PHP7.2
- 编写一个Csv.php类放入extend目录下,如下:
<?php
namespace csv;
use think\Db;
class Csv
{
/**
* 导出数据到CSV文件
* @param $fileName 生成的csv文件名
* @param array $title csv文件第一行显示标题
* @param array $showKeys csv文件对应显示的表中的字段名称
* @param string $table 查询的数据表
* @param string $pk 数据表的自增主键,此字段必须存在
* @param array $where 查询条件
*/
public function putCsv($fileName, $title = [], $showKeys = [], $table = '', $pk = 'id', $where = []) {
header('Content-Type: application/vnd.ms-excel');//设置内容类型为Excel
header('Content-Disposition: attachment;filename='.$fileName );//下载文件
header('Cache-Control: max-age=0');//表示当访问此网页后的0秒内再次访问不会去服务器
//打开文件或者 URL, php://output 是一个只写的数据流, 允许你以 print 和 echo 一样的方式 写入到输出缓冲区, a:写入方式打开,将文件指针指向文件末尾。如果文件不存在则尝试创建之。
$file = fopen('php://output',"a");
$limit =1000;
$calc = 0;
foreach ($title as $v){
$tit[] = iconv('UTF-8', 'GB2312//IGNORE',$v);//转码
}
//将行格式化为 CSV 并写入一个打开的文件中。(标题)
fputcsv($file,$tit);
//每次循环数
$step = 10000;
$j = $step;
//计算总数量
if (!empty($where)) {
$lastIdValue = Db::name($table)->where($where)->order($pk . ' DESC')->limit(1)->value($pk);
} else {
$lastIdValue = Db::name($table)->order($pk . ' DESC')->limit(1)->value($pk);
}
for ($i = 1;$i < $lastIdValue;$i = $i + $step) {
//这里获取生成器返回的生成器对象
if (!empty($where)) {
$cursor = Db::name($table)->where($pk, 'between', [$i, $j])->where($where)->cursor();
} else {
$cursor = Db::name($table)->where($pk, 'between', [$i, $j])->cursor();
}
foreach ($cursor as $v){
$calc++;
//-------核心!!!清空缓存,将缓存上的数据写入到文件--------
if($limit == $calc){
ob_flush();//将本来存在输出缓存中的内容取出来,调用ob_flush()之后缓冲区内容将被丢弃。
flush(); //待输出的内容立即发送。
$calc = 0;
}
//-------核心--------
$arr = [];
foreach ($showKeys as $key) {
if (isset($v[$key])) {
$arr[] = $v[$key];
}
}
foreach($arr as $t){
$tarr[] = iconv('UTF-8', 'GB2312//IGNORE',$t);
}
fputcsv($file,$tarr);//将行格式化为 CSV 并写入一个打开的文件中。(内容)
unset($tarr);//销毁指定的变量
}
$j = $j + $step;
}
//unset($list);//销毁指定的变量
fclose($file);//关闭打开的文件
}
}
2,控制器中调用类导出到CSV文件
public function testPutCsv() {
set_time_limit(0);
$fileName = 'test.csv';
$title = ['url地址', '添加时间', '状态'];
$showKeys = ['url', 'add_time', 'status'];
$where = [
'status' => 0
];
$csv = new Csv();
$csv->putCsv($fileName, $title, $showKeys, 'use_qrcode', 'id', $where);
}