You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PhpSpreadsheet读取大Xlsx文件缓存配置报错求助

解决PhpSpreadsheet读取大Xlsx文件时的缓存配置问题

问题背景

我需要读取一个包含40000行数据的大型Xlsx文件,在旧版本PHPExcel中使用缓存功能可正常运行。现迁移至最新版PhpSpreadsheet,必须配置缓存,否则程序会触发内存分配错误(已在php.ini中设置memory_limit = 5000M),错误信息如下:

Fatal error: Out of memory (allocated 780140544) (tried to allocate 29360128 bytes) in D:*\phpoffice\phpspreadsheet\src\PhpSpreadsheet\Collection\Cells.php on line 400

尝试的缓存配置

我尝试了APCu和Redis两种缓存组件,配置代码如下:

$client = new \Redis(); 
$client->connect('127.0.0.1', 6379); 
$pool = new \Cache\Adapter\Redis\RedisCachePool($client); 
// $pool = new \Cache\Adapter\Apcu\ApcuCachePool(); 
$simpleCache = new \Cache\Bridge\SimpleCache\SimpleCacheBridge($pool); 
\PhpOffice\PhpSpreadsheet\Settings::setCache($simpleCache); 

$objReader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader("Xlsx"); 
$objReader->setReadDataOnly(true); 
$objPHPExcel = $objReader->load(dirname(__FILE__).'/Tmpfile'.$i.'.xlsx'); 
$objPHPExcel->setActiveSheetIndex(0); 

foreach ( $objPHPExcel->getActiveSheet()->getRowIterator() as $row ) { 
    // 业务逻辑
}

新出现的错误

但两种配置均触发致命错误:

APCu缓存错误

APCu: Fatal error: Uncaught PhpOffice\PhpSpreadsheet\Exception: Cell entry A2 no longer exists in cache. This probably means that the cache was cleared by someone else. in D:\phpoffice\phpspreadsheet\src\PhpSpreadsheet\Collection\Cells.php:433 Stack trace: #0 D:\phpoffice\phpspreadsheet\src\PhpSpreadsheet\Worksheet\Worksheet.php(1239): PhpOffice\PhpSpreadsheet\Collection\Cells->get('A2') #1 D:\phpoffice\phpspreadsheet\src\PhpSpreadsheet\Worksheet\RowCellIterator.php(128): PhpOffice\PhpSpreadsheet\Worksheet\Worksheet->getCellByColumnAndRow(1, 2) #2 D:\Eclipse\WebShopUpdate\run.php(358): PhpOffice\PhpSpreadsheet\Worksheet\RowCellIterator->current() #3 {main} thrown in D:*\phpoffice\phpspreadsheet\src\PhpSpreadsheet\Collection\Cells.php on line 433

Redis缓存错误

Redis: Fatal error: Uncaught RedisException: Redis server went away in D:\cache\redis-adapter\RedisCachePool.php:82 Stack trace:
0 D:
\cache\redis-adapter\RedisCachePool.php(82): Redis->set('phpspreadsheet....', 'a:4:{i:0;b:1;i:...') #1
D:
\cache\adapter-common\AbstractCachePool.php(240): Cache\Adapter\Redis\RedisCachePool->storeItemInCache(Object(Cache\Adapter\Common\CacheItem), NULL) #2 D:\cache\simple-cache-bridge\SimpleCacheBridge.php(72): Cache\Adapter\Common\AbstractCachePool->save(Object(Cache\Adapter\Common\CacheItem))
3 D:
\phpoffice\phpspreadsheet\src\PhpSpreadsheet\Collection\Cells.php(372):
Cache\Bridge\SimpleCache\SimpleCacheBridge->set('phpspreadsheet....', Object(PhpOffice\PhpSpreadsheet\Cell\Cell)) #4 D:
\phpoffice\phpspreadsheet\src\PhpSpreadsheet\Collection\Cells.php(398): PhpOffice\PhpSpr in D:*\cache\adapter-common\AbstractCachePool.php on line 337

运行环境

环境1

  • PHP Version 7.1.3
  • Apache/2.4.25 (Win32)
  • APCu Version 5.1.11
  • Redis Version 4.0.2

环境2

  • PHP Version 5.6.30
  • Apache/2.4.25 (Win32)
  • APCu Version 4.0.10
  • Redis Version 2.2.7

两种环境下均出现相同错误,恳请解决该问题。


解决方案

先解决APCu缓存的问题

这个“Cell entry no longer exists”的错误,说白了就是缓存里的单元格数据丢了,大概率是APCu的缓存生命周期或者空间不够导致的,咱们试试这几个办法:

  1. 调大APCu的缓存参数:打开php.ini,把apc.ttl设成3600(1小时足够处理大文件了),apc.shm_size改成128M甚至更高,避免缓存被提前清理或者空间不够。
  2. 给PhpSpreadsheet的缓存加专属命名空间:默认缓存键没有隔离,很容易和其他应用的缓存冲突。咱们给缓存池加个唯一的命名空间,确保不会被误清:
$pool = new \Cache\Adapter\Apcu\ApcuCachePool();
// 用uniqid生成唯一命名空间,避免重复
$pool->setNamespace('phpspreadsheet_' . uniqid());
$simpleCache = new \Cache\Bridge\SimpleCache\SimpleCacheBridge($pool);
\PhpOffice\PhpSpreadsheet\Settings::setCache($simpleCache);
  1. 暂时禁用其他缓存清理操作:如果服务器上有定时脚本或者其他程序会清理APCu缓存,先暂时关掉,等处理完Excel文件再开。

再搞定Redis的连接问题

“Redis server went away”一般是连接断了,要么是超时,要么是数据太大扛不住,试试这些调整:

  1. 优化Redis连接参数:给连接加超时时间,开持久连接,避免处理大文件时连接被断开:
$client = new \Redis();
// 30秒超时,持久连接标识设成唯一的
$client->connect('127.0.0.1', 6379, 30, 'phpspreadsheet_conn');
// 如果Redis设了密码,记得加上认证
// $client->auth('your_redis_pass');
$pool = new \Cache\Adapter\Redis\RedisCachePool($client);
  1. 调整Redis服务器配置:打开redis.conf,把tcp-keepalive改成300,防止连接被主动回收;同时把maxmemory设得足够大,别让Redis因为内存不够把缓存数据删了。
  2. 换个更高效的序列化方式:默认的序列化效率低,试试用igbinary扩展(先在PHP里装好这个扩展),然后让Redis用它来序列化数据:
$client->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_IGBINARY);

通用的优化小技巧

不管用哪种缓存,这些操作都能帮你减少内存压力:

  1. 分批读取数据:别一次性把4万行都加载进来,用IReadFilter分批读,比如一次读1000行,处理完一批就清理缓存和对象:
class ChunkReadFilter implements \PhpOffice\PhpSpreadsheet\Reader\IReadFilter {
    private $startRow;
    private $endRow;

    public function __construct($startRow, $chunkSize) {
        $this->startRow = $startRow;
        $this->endRow = $startRow + $chunkSize;
    }

    public function readCell($column, $row, $worksheetName = '') {
        return $row >= $this->startRow && $row < $this->endRow;
    }
}

// 先拿到总行数
$worksheetInfo = $objReader->listWorksheetInfo()[0];
$totalRows = $worksheetInfo['totalRows'];

// 分批读取处理
for ($startRow = 1; $startRow <= $totalRows; $startRow += 1000) {
    $filter = new ChunkReadFilter($startRow, 1000);
    $objReader->setReadFilter($filter);
    $spreadsheet = $objReader->load(dirname(__FILE__).'/Tmpfile'.$i.'.xlsx');
    
    // 处理当前批次的行数据
    $sheet = $spreadsheet->getActiveSheet();
    foreach ($sheet->getRowIterator() as $row) {
        // 你的业务逻辑
    }
    
    // 手动清理,释放内存
    unset($spreadsheet, $sheet);
    gc_collect_cycles();
}
  1. 更新PhpSpreadsheet到最新版:旧版本可能有缓存相关的bug,用composer update phpoffice/phpspreadsheet更到最新稳定版试试。
  2. 关闭无用功能:除了setReadDataOnly(true),再加个$objReader->setReadEmptyCells(false);,不读空单元格,减少缓存的数据量。

内容的提问来源于stack exchange,提问作者Ekhrikhor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:18:01