使用PHPExcel导出PHP数据至Excel速度极慢,求排查优化
回答:
核心性能瓶颈分析与优化方案
你的代码里有两个致命的性能问题,这也是导致3000条记录导出耗时超30分钟的主要原因,下面逐个解决:
1. 彻底解决N+1数据库查询问题
你在循环中每条记录都调用$data_pic = $this->App_m->detilpic_m($row_aplikasi->idpic),这意味着3000条记录会触发3000次独立的数据库查询——数据库的连接、查询、结果返回开销会被放大到极致,这是最影响速度的点。
优化步骤:
- 先批量获取所有需要的
pic数据,存入数组做映射,循环时直接从数组取值,避免重复查询。
修改控制器代码:
function download_aplikasi() { $this->Weblog_m->save_log_m("Download aplikasi"); $data_aplikasi = $this->Io_excel_m->get_aplikasi_m(); // --- 新增:批量获取pic数据并构建映射 --- $idpic_list = array_column($data_aplikasi, 'idpic'); $idpic_list = array_unique(array_filter($idpic_list)); // 去重并过滤空值 $pic_data = $this->App_m->get_batch_pic_m($idpic_list); $pic_map = []; foreach($pic_data as $pic){ $pic_map[$pic->idpic] = $pic; } // --- 批量获取结束 --- // Create new PHPExcel object $objPHPExcel = new PHPExcel(); $objPHPExcel->getActiveSheet()->getStyle('1:1')->getFont()->setBold(true); // ... 表头设置部分保持不变 ... $i = 1; if (count($data_aplikasi) > 0) { foreach ($data_aplikasi as $row_aplikasi) { // --- 替换原来的单条查询 --- $data_pic = isset($pic_map[$row_aplikasi->idpic]) ? $pic_map[$row_aplikasi->idpic] : (object)['nama_user' => '', 'nomorhp' => '']; // --- 替换结束 --- $i = $i + 1; // ... 单元格赋值部分保持不变 ... } } else { // ... 无数据处理部分保持不变 ... } // ... 后续输出部分保持不变 ... }
在模型中新增批量查询方法:
function get_batch_pic_m($idpic_list){ if(empty($idpic_list)) return []; $this->db->where_in('idpic', $idpic_list); // 假设你的pic表是tbl_pic,根据实际表名调整 return $this->db->get('tbl_pic')->result(); }
2. 优化PHPExcel单元格写入效率
你现在用链式setCellValue逐个设置单元格,3000条×29列=87000次调用,每次调用都会触发PHPExcel的内部状态更新和校验,开销极大。换成批量写入的方式,速度能提升数倍。
优化步骤:
- 构建二维数组存储所有数据,用
fromArray方法一次性写入,减少PHPExcel的内部操作次数。
修改控制器代码(表头+数据写入部分):
// Create new PHPExcel object $objPHPExcel = new PHPExcel(); // --- 批量设置表头 --- $header = [ 'TGL_APLIKASI', 'CD_CABANG', 'NAMA_CABANG', 'NIP', 'NAMA_PEGAWAI', 'JABATAN', 'NAMA_NASABAH', 'NO_REK', 'NO_CIF', 'DOB_NASABAH', 'JNS_NASABAH', 'NOMINAL', 'ID_NASABAH', 'PRODUK', 'JNS_PINJAMAN', 'JNS_USAHA', 'LAMA_USAHA', 'JNS_JAMINAN', 'PIC_NASABAH', 'TELP_NASABAH', 'NAMA_TOKO', 'KATEGORI', 'CATATAN', 'KETERANGAN', 'PIC_UNIT', 'TELP_PIC', 'KAT_PRODUK', 'PROGRESS', 'PROGRESS_NOTE' ]; $objPHPExcel->getActiveSheet()->fromArray([$header], null, 'A1'); $objPHPExcel->getActiveSheet()->getStyle('1:1')->getFont()->setBold(true); // --- 表头设置结束 --- // --- 批量构建数据数组 --- $excel_data = []; if (count($data_aplikasi) > 0) { foreach ($data_aplikasi as $row_aplikasi) { $data_pic = isset($pic_map[$row_aplikasi->idpic]) ? $pic_map[$row_aplikasi->idpic] : (object)['nama_user' => '', 'nomorhp' => '']; $excel_data[] = [ date("d/m/Y", strtotime($row_aplikasi->tglaplikasi)), $row_aplikasi->cdcabang, $row_aplikasi->namacabang, $row_aplikasi->nip, $row_aplikasi->nama_user, $row_aplikasi->namajabatan, $row_aplikasi->nama_nasabah, $row_aplikasi->norek, $row_aplikasi->nocif, $row_aplikasi->tgllahir, $row_aplikasi->jnsnasabah, $row_aplikasi->nominal_aplikasi, $row_aplikasi->ktpnasabah, $row_aplikasi->produk, $row_aplikasi->jnspinjaman, $row_aplikasi->jnsusaha, $row_aplikasi->lamausaha, $row_aplikasi->jnsjaminan, $row_aplikasi->pic_nasabah, $row_aplikasi->telpnasabah, $row_aplikasi->namatoko, $row_aplikasi->kategori, $row_aplikasi->catatan, $row_aplikasi->keterangan, $data_pic->nama_user, $data_pic->nomorhp, $row_aplikasi->namaproduct, $row_aplikasi->progress, $row_aplikasi->progress_note ]; } } else { // 无数据时填充空列,保持表头对齐 $excel_data[] = array_merge(['No Record Found'], array_fill(1, 28, '')); } // 批量写入数据,从A2开始 $objPHPExcel->getActiveSheet()->fromArray($excel_data, null, 'A2'); // --- 数据写入结束 ---
3. 其他辅助优化
数据库查询优化
你的get_aplikasi_m模型代码不完整,缺少返回语句,而且用了select('*')会查询不必要的字段,增加数据传输和内存占用。修改如下:
function get_aplikasi_m() { $periode = $this->input->post("bulan_kinerja"); $idcabang = $this->input->post("id_cabang"); $cek_cabang = $this->cek_cabang_m($idcabang); // 只选择需要的字段,避免查询冗余数据 $this->db->select(' tbl_aplikasi.tglaplikasi, tbl_aplikasi.cdcabang, tbl_aplikasi.namacabang, tbl_aplikasi.nip, tbl_user.nama_user, tbl_aplikasi.namajabatan, tbl_aplikasi.nama_nasabah, tbl_aplikasi.norek, tbl_aplikasi.nocif, tbl_aplikasi.tgllahir, tbl_aplikasi.jnsnasabah, tbl_aplikasi.nominal_aplikasi, tbl_aplikasi.ktpnasabah, tbl_aplikasi.produk, tbl_aplikasi.jnspinjaman, tbl_aplikasi.jnsusaha, tbl_aplikasi.lamausaha, tbl_aplikasi.jnsjaminan, tbl_aplikasi.pic_nasabah, tbl_aplikasi.telpnasabah, tbl_aplikasi.namatoko, tbl_aplikasi.kategori, tbl_aplikasi.catatan, tbl_aplikasi.keterangan, tbl_aplikasi.idpic, tbl_aplikasi.namaproduct, tbl_aplikasi.progress, tbl_aplikasi.progress_note '); $this->db->from("tbl_aplikasi"); $this->db->join("tbl_user", "tbl_user.iduser = tbl_aplikasi.iduser", "Left"); // 加上periode筛选条件(你原来的代码里遗漏了) if($periode){ $this->db->where('YEAR(tbl_aplikasi.tglaplikasi)', date('Y', strtotime($periode))); $this->db->where('MONTH(tbl_aplikasi.tglaplikasi)', date('m', strtotime($periode))); } // 加上idcabang筛选条件 if($idcabang){ $this->db->where('tbl_aplikasi.cdcabang', $idcabang); } return $this->db->get()->result(); }
PHPExcel内存与计算优化
如果数据量还是很大,可以开启磁盘缓存减少内存占用,同时
相关产品推荐
相关产品推荐

