เทคนิคการดาวน์โหลด Excel 200,000 แถว ด้วย PHP 8 และ CodeIgniter 3 (เสถียร 100% RAM ไม่เต็ม)
การทำรายงานส่งออกไฟล์ Excel ขนาดใหญ่ (.xlsx) บนเว็บแอปพลิเคชัน มักประสบปัญหาหลักสองอย่างคือ หน่วยความจำเต็ม (Out of Memory) และ เว็บค้างจนหมดเวลา (Execution Timeout) โดยเฉพาะถ้าเราใช้ Library ยอดนิยมอย่าง PhpSpreadsheet แบบปกติ เมื่อมีข้อมูลเกินแสนแถว ระบบก็พร้อมที่จะล่มได้ทุกเมื่อ
บทความนี้จะมาแบ่งปันแนวทางแก้ไขปัญหาด้วยการผสานพลังของ PHP 8, OpenSpout (Streaming Library) และการทำ Unbuffered Query ใน CodeIgniter 3 ที่จะช่วยให้ระบบใช้ RAM คงที่เพียง 15-20 MB ตลอดระยะเวลาการดาวน์โหลดข้อมูลสองแสนแถวครับ
1. แนวคิดหลัก: Streaming & Unbuffered Query
หากต้องการประหยัดหน่วยความจำ เราจำเป็นต้องหลีกเลี่ยงการนำข้อมูลทั้งหมดเข้าสู่ RAM ของ PHP ในคราวเดียว:
- Unbuffered Query: จะดึงข้อมูลจากฐานข้อมูลขึ้นมาประมวลผลทีละ 1 แถวแทนการโหลดขึ้นมาเป็น Array ขนาดใหญ่
- Streaming Writer: OpenSpout จะทำการเขียนข้อมูลลงใน Temp File ทันที และสตรีมข้อมูลผลลัพธ์ยิงตรงไปที่หน้าจอผู้ใช้ โดยไม่ต้องเก็บข้อมูลทุกแถวไว้ใน RAM
2. ขั้นตอนการตั้งค่าใน CodeIgniter 3
ขั้นตอนที่ 2.1: เปิดใช้งาน Composer Autoload
เปิดไฟล์ application/config/config.php และตรวจสอบให้แน่ใจว่าได้เปิดใช้งาน Autoload ของ Composer แล้ว:
$config['composer_autoload'] = TRUE;
ขั้นตอนที่ 2.2: ติดตั้ง OpenSpout ผ่าน Composer
รันคำสั่งนี้ที่ Root Directory ของโปรเจกต์คุณผ่าน Terminal:
composer require openspout/openspout
3. เขียนโค้ดใน Controller
สร้างไฟล์ Controller ที่ application/controllers/ExcelExport.php:
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
use OpenSpout\Writer\XLSX\Writer;
use OpenSpout\Common\Entity\Row;
use OpenSpout\Common\Entity\Cell;
class ExcelExport extends CI_Controller {
public function __construct() {
parent::__construct();
$this->load->database();
}
public function download() {
// 1. ปลดล็อกข้อจำกัดของระบบชั่วคราว
set_time_limit(0);
ini_set('memory_limit', '128M');
// 2. เตรียม Writer ของ OpenSpout สตรีมไปเบราว์เซอร์
$writer = new Writer();
$fileName = 'report_' . date('Ymd_His') . '.xlsx';
$writer->openToBrowser($fileName);
// 3. เขียนแถวหัวตาราง (Header)
$headers = ['รหัส', 'ชื่อลูกค้า', 'อีเมล', 'เบอร์โทรศัพท์', 'คะแนนสะสม'];
$headerCells = [];
foreach ($headers as $title) {
$headerCells[] = Cell::fromValue($title);
}
$writer->addRow(new Row($headerCells));
// 4. ใช้ unbuffered_row('array') เพื่อประหยัด RAM
$sql = "SELECT id, name, email, phone, points FROM customers";
$query = $this->db->query($sql);
while ($row = $query->unbuffered_row('array')) {
$cells = [
Cell::fromValue($row['id']),
Cell::fromValue($row['name']),
Cell::fromValue($row['email']),
Cell::fromValue($row['phone']),
Cell::fromValue((int)$row['points']),
];
$writer->addRow(new Row($cells));
}
// 5. ปิดการเชื่อมต่อเขียนไฟล์
$writer->close();
exit;
}
}
4. ข้อควรระวังในการดาวน์โหลดข้อมูลมหาศาล
ตรวจสอบให้แน่ใจว่าไม่มีคำสั่ง echo, print หรือข้อความ Error/Warning แสดงผลก่อนหรือระหว่างกระบวนการดาวน์โหลด เนื่องจากจะทำให้ตัวเข้ารหัสไฟล์ Excel ผิดพลาดและดาวน์โหลดไฟล์ชำรุดเสียหายเปิดใช้งานไม่ได้
การแต่งสีหรือกำหนดฟอนต์ที่หลากหลายในทุกเซลล์ข้อมูลจำทำให้การสร้างไฟล์ XML ภายหลังทำงานช้าลงอย่างมาก แนะนำให้ตกแต่งสีสันเฉพาะแถวหัวข้อ (Header) และปล่อยให้แถวข้อมูลที่เหลือมีรูปแบบเป็นค่าตั้งต้น
สรุปผลลัพธ์
ด้วยโครงสร้างของ PHP 8 ที่ประมวลผลได้ดีขึ้น ประกอบกับการดึงข้อมูลทีละแถวด้วย unbuffered_row() ใน CI3 และการสตรีมเขียนลงดิสก์ด้วย OpenSpout จะทำให้การดึงข้อมูล 200,000 แถว กลายเป็นเรื่องง่ายและใช้ทรัพยากรเครื่องเซิร์ฟเวอร์ต่ำมากอย่างไม่น่าเชื่อครับ!

ความคิดเห็น
แสดงความคิดเห็น