PHP导出Excel时数据量过大的问题

1、设置脚本运行时间

set_time_limit(0)

2、运行内存设置
当数据量比较大时就需要设置memory_limit,来防止内存报错,但是这终究不是解决办法,因为系统的内存是有限的,比如你设置为1G,那么当10个人来同时调用的时候,会占用10G内存。

3、关于Excel软件的限制

Excel 2003及以下的版本。一张表最大支持65536行数据,256列。
Excel 2007-2010版本。一张表最大支持1048576行,16384列。

所以从实现上来看,还是有上限的,这一点需要明确。

4、关于PHPExcel的问题
这是我们常用的导出Excel的工具,但是它也有自身的局限性。因为PHPExcel最终是将所有的数据以及设置全部存储在一个对象里面,最终再写入文件或者输出流,而这个变量就是局限点,它是受运行内存限制的,所以我们发现PHPExcel的承载量不大,很容易就出现内存溢出。

从1.7.3开始,它支持设置cell的缓存方式,

PHPExcel平均下来使用1k/单元格的内存,因此大的文档会导致内存消耗的也很快。单元格缓存机制能够允许PHPExcel将内存中的小的单元格对象缓存在磁盘或者APC,memcache或者Wincache中,尽管会在读取数据上消耗一些时间,但是能够帮助你降低内存的消耗。

默认情况下,PHPExcel依然将单元格对象保存在内存中,但是你可以自定义。你可以使用PHPExcel_Settings::setCacheStorageMethod()方法,将缓存方式作为参数传递给这个方法来设置缓存的方式。

每一个worksheet都会有一个独立的缓存,当一个worksheet实例化时,就会根据设置或配置的缓存方式来自动创建。一旦你开始读取一个文件或者你已经创建了第一个worksheet,就不能在改变缓存的方式了。

方法1:

$cacheMethod = PHPExcel_CachedObjectStorageFactory::cache_in_memory;
$bool = PHPExcel_Settings::setCacheStorageMethod($cacheMethod);

默认是内存缓存的方式

方式2:

PHPExcel_CachedObjectStorageFactory::cache_in_memory_serialized;

使用这种缓存方式,单元格会以序列化的方式保存在内存中,这是降低内存使用率性能比较高的一种方案。

方式3:

PHPExcel_CachedObjectStorageFactory::cache_in_memory_gzip;

与序列化的方式类似,这种方法在序列化之后,又进行gzip压缩之后再放入内存中,这回跟进一步降低内存的使用,但是读取和写入时会有一些慢。

方式4:

PHPExcel_CachedObjectStorageFactory::cache_to_discISAM;
PHPExcel_CachedObjectStorageFactory::cache_to_phpTemp;

类似cache_to_discISAM这种方式,使用cache_to_phpTemp时,所有的单元格会还存在php://temp I/O流中,只把他们的位置保存在PHP的内存中。PHP的php://memory包裹器将数据保存在内存中,php://temp的行为类似,但是当存储的数据大小超过内存限制时,会将数据保存在临时文件中,默认的大小是1MB,但是你可以在初始化时修改它:

$cacheMethod = PHPExcel_CachedObjectStorageFactory:: cache_to_phpTemp;

$cacheSettings = array( ' memoryCacheSize '  => '8MB'  );

PHPExcel_Settings::setCacheStorageMethod($cacheMethod, $cacheSettings);

php://temp文件在脚本结束是会自动删除。

方式5:

PHPExcel_CachedObjectStorageFactory::cache_to_apc;

当使用cach_to_apc时,单元格保存在APC中,只在内存中保存索引。APC缓存默认超时时间时600秒,对绝大多数应用是足够了,当然你也可以在初始化时进行修改:

$cacheMethod = PHPExcel_CachedObjectStorageFactory::cache_to_APC;

$cacheSettings = array( 'cacheTime'  => 600   );

PHPExcel_Settings::setCacheStorageMethod($cacheMethod, $cacheSettings);

当脚本运行结束时,所有的数据都会从APC中清楚(忽略缓存时间),不能使用此机制作为持久缓存。

方式6:

PHPExcel_CachedObjectStorageFactory::cache_to_memcache

$cacheMethod = PHPExcel_CachedObjectStorageFactory::cache_to_memcache;

$cacheSettings = array( 'memcacheServer'  => 'localhost',

    'memcachePort'    => 11211,

    'cacheTime'       => 600
);

PHPExcel_Settings::setCacheStorageMethod($cacheMethod, $cacheSettings);

从初始化设置的形式上看,MS还不支持多台memcache服务器轮询的方式,比较遗憾。
当脚本结束时,所有的数据都会从memcache清空(忽略缓存时间),不能使用该机制进行持久存储。

方式7:

PHPExcel_CachedObjectStorageFactory::cache_to_wincache;

使用cache_to_wincache方式,单元格对象会保存在Wincache中,只在内存中保存索引,默认情况下Wincache过期时间为600秒,对绝大多数应用是足够了,当然也可以在初始化时修改:

$cacheMethod = PHPExcel_CachedObjectStorageFactory::cache_to_wincache;

$cacheSettings = array( 'cacheTime'  => 600 );

PHPExcel_Settings::setCacheStorageMethod($cacheMethod, $cacheSettings);

5、关于csv
很多时候,可以使用csv文件代替Excel文件,一是因为csv比excel轻量,二是csv可以边写边输出,不像excel必须运算完才能输出到浏览器。

虽然csv文件是普通文件,没有明显的大小限制,但是绝大部分csv还是用Excel来打开的,所以还是受制于Excel的上限限制。

所以,对于超大数据量,建议导出到多个csv文件,然后使用zip压缩后让用户下载。最后将多余的文件删掉。

zip压缩包:ZipArchive 类

设置zip header:header("Content-Type: application/zip");

csv 的边运算边输出主要依赖函数 fputcsv

public function articleAccessLog($timeStart, $timeEnd)
{
	set_time_limit(0);
	$columns = [
		'文章ID', '文章标题', ......
	];
	$csvFileName = '用户日志' . $timeStart .'_'. $timeEnd . '.xlsx';
	//设置好告诉浏览器要下载excel文件的headers
	header('Content-Description: File Transfer');
	header('Content-Type: application/vnd.ms-excel');
	header('Content-Disposition: attachment; filename="'. $fileName .'"');
	header('Expires: 0');
	header('Cache-Control: must-revalidate');
	header('Pragma: public');
	$fp = fopen('php://output', 'a');//打开output流
	mb_convert_variables('GBK', 'UTF-8', $columns);
	fputcsv($fp, $columns);//将数据格式化为CSV格式并写入到output流中
	$accessNum = '1000000'//从数据库获取总量,假设是一百万
	$perSize = 1000;//每次查询的条数
	$pages   = ceil($accessNum / $perSize);
	$lastId  = 0;
	for($i = 1; $i <= $pages; $i++) {
		$accessLog = $logService->getArticleAccessLog($timeStart, $timeEnd, $lastId, $perSize);
		foreach($accessLog as $access) {
			$rowData = [
				......//每一行的数据
			];
			mb_convert_variables('GBK', 'UTF-8', $rowData);
			fputcsv($fp, $rowData);
			$lastId = $access->id;
		}
		unset($accessLog);//释放变量的内存
		//刷新输出缓冲到浏览器
		ob_flush();
		flush();//必须同时使用 ob_flush() 和flush() 函数来刷新输出缓冲。
	}
	fclose($fp);
	exit();
}

6、关于大量数据集查询返回的问题
我们通常需要查询Mysql,然后将结果集遍历来处理,最后将处理完的结果写入Excel,那么大数据量的遍历也会导致内存溢出。

无论是使用Mysqli还是PDO,返回的结果集都是对象,尽量不要直接转数组再遍历,而是使用while遍历结果集,这样是每次读取一行,占用内存较小,因为其内部使用的是游标的方式而不是整个加载到内存,当然也可以使用 foreach+yield 代替 while,效果是一样的。

foreach (mysqli_query($con, $sql, MYSQLI_USE_RESULT) as $row)
{
    yield $row;
}

while(mysqli_query($con, $sql, MYSQLI_USE_RESULT) as $row)
{
    // return $row;
}

如果你的网站经常需要导出大表的话,建议安装c扩展来处理excel。
请参考 https://blog.csdn.net/raoxiaoya/article/details/105991278

你可能感兴趣的:(PHP)