導出百萬級數據的多種方法(親測可用)

主要參考最後兩個方法

<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use ZipArchive;

class TestController extends Controller
{
    /**
     * 忽略
     * PS:
     * 1.mb_convert_encoding,iconv 可能會導致內存溢出
     * 2.使用fputcsv()
     */
    public function indexTwo()
    {
        set_time_limit(0);
        Log::info("IndexTwo起始時間:" . date('Y-m-d H:i:s', time()));

        header('Content-Type: application/vnd.ms-excel');
        header('Content-Disposition: attachment;filename=2.csv');
        header('Cache-Control: max-age=0');

        $myfile = fopen('php://output', 'a');
        $headlist = [
            '編號1',
            '編號2',
            '編號3'
        ];
        //輸出Excel列名信息
        foreach ($headlist as $key => $value) {
            //CSV的Excel支持GBK編碼,一定要轉換,否則亂碼
            $headlist[$key] = iconv('UTF-8', 'GBK', $value);
        }
        //將數據通過fputcsv寫到文件句柄
        fputcsv($myfile, $headlist);
        $select = [
            "orders.*",
            "order_senders.sender_name",
            "order_senders.sender_phone",
            "order_senders.sender_province_name",
            "order_senders.sender_city_name",
            "order_senders.sender_county_name",
            "po.order_number as parent_order_number",
            "order_senders.sender_street_name",
            "order_senders.sender_detail",
            "orders.order_status",
            "order_expresses.express_company_name",
            "order_expresses.weight_from_express",
            "order_expresses.freight",
            "order_expresses.remark_from_express",
            "order_senders.order_id",
            "order_expresses.express_number",
            "order_expresses.express_company_code",
            "warehouses.warehouse_name",
            "order_expresses.id as order_expressesId",
            DB::raw("( SELECT GROUP_CONCAT( order_waybills.warehouse_signed_at ) FROM order_waybills WHERE order_expresses.id = order_waybills.order_express_id ) AS waybill_signCreatedAt"),
            DB::raw("( SELECT GROUP_CONCAT( order_waybills.tracking_number ) FROM order_waybills WHERE order_expresses.id = order_waybills.order_express_id ) AS tracking_number"),
            DB::raw("( SELECT GROUP_CONCAT( order_waybills.express_signed_at ) FROM order_waybills WHERE order_expresses.id = order_waybills.order_express_id) AS express_signed_at"),
        ];
        $query = DB::table('orders')
            ->leftJoin('order_expresses', 'orders.id', '=', 'order_expresses.order_id')
            ->leftJoin('order_senders', 'orders.id', '=', 'order_senders.order_id')
            ->leftJoin('orders as po', 'po.id', '=', 'orders.parent_id')
            ->leftJoin('order_receivers', 'orders.id', '=', 'order_receivers.order_id')
            ->leftJoin('warehouses', 'order_receivers.warehouse_code', '=', 'warehouses.warehouse_code')
            ->orderBy('orders.created_at', 'desc');

        $query->where('orders.created_at', '>', '2019-1-23')
            ->where('orders.created_at', '<', '2019-4-10')
            ->select($select)->chunk(20000, function ($terminal) use (&$myfile) {
                $data = [];
                foreach ($terminal as $key => $value) {
                    $data['sender_name'] = mb_convert_encoding($value->sender_name, 'GBK', 'utf-8');
                    $data['sender_phone'] = mb_convert_encoding($value->sender_phone, 'GBK', 'utf-8');
                    $data['sender_province_name'] = mb_convert_encoding($value->sender_province_name, 'GBK', 'utf-8');
                    $data['sender_city_name'] = mb_convert_encoding($value->sender_city_name, 'GBK', 'utf-8');
                    $data['sender_county_name'] = mb_convert_encoding($value->sender_county_name, 'GBK', 'utf-8');
                    $data['parent_order_number'] = mb_convert_encoding($value->parent_order_number, 'GBK', 'utf-8');
                    $data['sender_street_name'] = mb_convert_encoding($value->sender_street_name, 'GBK', 'utf-8');
                    $data['sender_detail'] = mb_convert_encoding($value->sender_detail, 'GBK', 'utf-8');
                    $data['order_status'] = mb_convert_encoding($value->order_status, 'GBK', 'utf-8');
                    $data['express_company_name'] = mb_convert_encoding($value->express_company_name, 'GBK', 'utf-8');
                    $data['weight_from_express'] = mb_convert_encoding($value->weight_from_express, 'GBK', 'utf-8');
                    $data['freight'] = mb_convert_encoding($value->freight, 'GBK', 'utf-8');
                    $data['remark_from_express'] = mb_convert_encoding($value->remark_from_express, 'GBK', 'utf-8');
                    $data['order_id'] = mb_convert_encoding($value->order_id, 'GBK', 'utf-8');
                    $data['express_number'] = mb_convert_encoding($value->express_number, 'GBK', 'utf-8');
                    $data['express_company_code'] = mb_convert_encoding($value->express_company_code, 'GBK', 'utf-8');
                    $data['warehouse_name'] = mb_convert_encoding($value->warehouse_name, 'GBK', 'utf-8');
                    $data['order_expressesId'] = mb_convert_encoding($value->order_expressesId, 'GBK', 'utf-8');
                    $data['waybill_signCreatedAt'] = mb_convert_encoding($value->waybill_signCreatedAt, 'GBK', 'utf-8');
                    $data['tracking_number'] = mb_convert_encoding($value->tracking_number, 'GBK', 'utf-8');
                    $data['express_signed_at'] = mb_convert_encoding($value->express_signed_at, 'GBK', 'utf-8');
                    fputcsv($myfile, $data);
                }
                unset($data);
            });

        fclose($myfile);

        Log::info("IndexTwo截止時間:" . date('Y-m-d H:i:s', time()));
    }

    /**
     * 略
     * 使用 offset limit 獲取數據
     * PS:多次使用mb_convert_encoding,可能導致內存泄漏
     */
    public function indexFour()
    {
        set_time_limit(0);

        header('Content-Encoding: UTF-8');
        header("Content-type:application/vnd.ms-excel;charset=UTF-8");
        header('Content-Disposition: attachment;filename=3.csv');

        //打開php標準輸出流以寫入追加的方式打開
        $fp = fopen('php://output', 'a');

        //用fputcsv從數據庫中導出1百萬的數據,比如我們每次取1萬條數據,分100步來執行
        $nums = 10000;

        //設置標題
        $title = array('id', '編號', '姓名', '年齡'); //注意這裏是小寫id,否則ID命名打開會提示Excel 已經檢測到"xxx.xsl"是SYLK文件,但是不能將其加載: CSV 文或者XLS文件的前兩個字符是大寫字母"I","D"時,會發生此問題。
        foreach ($title as $key => $item)
            $title[$key] = iconv("UTF-8", "GB2312//IGNORE", $item);

        fputcsv($fp, $title);

        // 獲取數據
        $select = [
            "orders.*",
            "order_senders.sender_name",
            "order_senders.sender_phone",
            "order_senders.sender_province_name",
            "order_senders.sender_city_name",
            "order_senders.sender_county_name",
            "po.order_number as parent_order_number",
            "order_senders.sender_street_name",
            "order_senders.sender_detail",
            "orders.order_status",
            "order_expresses.express_company_name",
            "order_expresses.weight_from_express",
            "order_expresses.freight",
            "order_expresses.remark_from_express",
            "order_senders.order_id",
            "order_expresses.express_number",
            "order_expresses.express_company_code",
            "warehouses.warehouse_name",
            "order_expresses.id as order_expressesId",
            DB::raw("( SELECT GROUP_CONCAT( order_waybills.warehouse_signed_at ) FROM order_waybills WHERE order_expresses.id = order_waybills.order_express_id ) AS waybill_signCreatedAt"),
            DB::raw("( SELECT GROUP_CONCAT( order_waybills.tracking_number ) FROM order_waybills WHERE order_expresses.id = order_waybills.order_express_id ) AS tracking_number"),
            DB::raw("( SELECT GROUP_CONCAT( order_waybills.express_signed_at ) FROM order_waybills WHERE order_expresses.id = order_waybills.order_express_id) AS express_signed_at"),
        ];
        $query = DB::table('orders')
            ->select($select)
            ->leftJoin('order_expresses', 'orders.id', '=', 'order_expresses.order_id')
            ->leftJoin('order_senders', 'orders.id', '=', 'order_senders.order_id')
            ->leftJoin('orders as po', 'po.id', '=', 'orders.parent_id')
            ->leftJoin('order_receivers', 'orders.id', '=', 'order_receivers.order_id')
            ->leftJoin('warehouses', 'order_receivers.warehouse_code', '=', 'warehouses.warehouse_code')
            ->where('orders.created_at', '>', '2019-3-28')
            ->where('orders.created_at', '<', '2019-3-29')
            ->orderBy('orders.created_at', 'desc');
        // 總記錄數
        $count = $query->count();
        // 分批次數
        $step = ceil($count / $nums);
        for ($s = 1; $s <= $step; $s++) {
            $start = ($s - 1) * $nums;
            $data = $query->offset($start)->limit($nums)->get();
            foreach ($data as $key => $item) {
                $tmp = [];
                $tmp['sender_name'] = mb_convert_encoding($item->sender_name, 'GBK', 'utf-8');
                $tmp['sender_phone'] = mb_convert_encoding($item->sender_phone, 'GBK', 'utf-8');
                $tmp['sender_province_name'] = mb_convert_encoding($item->sender_province_name, 'GBK', 'utf-8');
                $tmp['sender_city_name'] = mb_convert_encoding($item->sender_city_name, 'GBK', 'utf-8');
                $tmp['sender_county_name'] = mb_convert_encoding($item->sender_county_name, 'GBK', 'utf-8');
                $tmp['parent_order_number'] = mb_convert_encoding($item->parent_order_number, 'GBK', 'utf-8');
                $tmp['sender_street_name'] = mb_convert_encoding($item->sender_street_name, 'GBK', 'utf-8');
                $tmp['sender_detail'] = mb_convert_encoding($item->sender_detail, 'GBK', 'utf-8');
                $tmp['order_status'] = mb_convert_encoding($item->order_status, 'GBK', 'utf-8');
                $tmp['express_company_name'] = mb_convert_encoding($item->express_company_name, 'GBK', 'utf-8');
                $tmp['weight_from_express'] = mb_convert_encoding($item->weight_from_express, 'GBK', 'utf-8');
                $tmp['freight'] = mb_convert_encoding($item->freight, 'GBK', 'utf-8');
                $tmp['remark_from_express'] = mb_convert_encoding($item->remark_from_express, 'GBK', 'utf-8');
                $tmp['order_id'] = mb_convert_encoding($item->order_id, 'GBK', 'utf-8');
                $tmp['express_number'] = mb_convert_encoding($item->express_number, 'GBK', 'utf-8');
                $tmp['express_company_code'] = mb_convert_encoding($item->express_company_code, 'GBK', 'utf-8');
                $tmp['warehouse_name'] = mb_convert_encoding($item->warehouse_name, 'GBK', 'utf-8');
                $tmp['order_expressesId'] = mb_convert_encoding($item->order_expressesId, 'GBK', 'utf-8');
                $tmp['waybill_signCreatedAt'] = mb_convert_encoding($item->waybill_signCreatedAt, 'GBK', 'utf-8');
                $tmp['tracking_number'] = mb_convert_encoding($item->tracking_number, 'GBK', 'utf-8');
                $tmp['express_signed_at'] = mb_convert_encoding($item->express_signed_at, 'GBK', 'utf-8');
                fputcsv($fp, $tmp);
            }
            unset($data);
            ob_flush();  //每1萬條數據就刷新緩衝區
            flush();
        }
        fclose($fp);
    }

    /**
     * 分片查詢寫入文件 合併 壓縮後導出
     */
    public function indexFive()
    {
        Log::info("起始時間:" . date('Y-m-d H:i:s', time()));
        set_time_limit(0);
        $select = [
            "orders.*",
            "order_senders.sender_name",
            "order_senders.sender_phone",
            "order_senders.sender_province_name",
            "order_senders.sender_city_name",
            "order_senders.sender_county_name",
            "po.order_number as parent_order_number",
            "order_senders.sender_street_name",
            "order_senders.sender_detail",
            "orders.order_status",
            "order_expresses.express_company_name",
            "order_expresses.weight_from_express",
            "order_expresses.freight",
            "order_expresses.remark_from_express",
            "order_senders.order_id",
            "order_expresses.express_number",
            "order_expresses.express_company_code",
            "warehouses.warehouse_name",
            "order_expresses.id as order_expressesId",
            DB::raw("( SELECT GROUP_CONCAT( order_waybills.warehouse_signed_at ) FROM order_waybills WHERE order_expresses.id = order_waybills.order_express_id ) AS waybill_signCreatedAt"),
            DB::raw("( SELECT GROUP_CONCAT( order_waybills.tracking_number ) FROM order_waybills WHERE order_expresses.id = order_waybills.order_express_id ) AS tracking_number"),
            DB::raw("( SELECT GROUP_CONCAT( order_waybills.express_signed_at ) FROM order_waybills WHERE order_expresses.id = order_waybills.order_express_id) AS express_signed_at"),
        ];
        $query = DB::table('orders')
            ->select($select)
            ->leftJoin('order_expresses', 'orders.id', '=', 'order_expresses.order_id')
            ->leftJoin('order_senders', 'orders.id', '=', 'order_senders.order_id')
            ->leftJoin('orders as po', 'po.id', '=', 'orders.parent_id')
            ->leftJoin('order_receivers', 'orders.id', '=', 'order_receivers.order_id')
            ->leftJoin('warehouses', 'order_receivers.warehouse_code', '=', 'warehouses.warehouse_code')
            ->where('orders.created_at', '>', '2019-1-23')
            ->where('orders.created_at', '<', '2019-4-10')
            ->orderBy('orders.created_at', 'desc');
        // 總記錄數
        $sqlCount = $query->count();
        // 單個Excel記錄條數
        $sqlLimit = 100000;
        // 循環記錄初始值
        $cnt = 0;
        // 分割文件名數組
        $fileNameArr = [];
        // 分割文件名前綴標識
        $mark = "mark";
        // Excel列頭信息
        $headArr = ['編號', '編號', '編號', '編號', '編號', '編號', '編號', '編號', '編號', '編號', '編號', '編號', '編號', '編號', '編號'];

        // 檢測Excel臨時文件夾是否存在(按日期生成目錄)
        $fileTmpDir = public_path('Export/FileTmp/' . date('Ymd'));
        if (!is_dir($fileTmpDir))
            mkdir($fileTmpDir, 0777, true);

        $bom = chr(0xEF) . chr(0xBB) . chr(0xBF);
        // 寫入數據
        for ($i = 0; $i < ceil($sqlCount / $sqlLimit); $i++) {
            $fileName = $fileTmpDir . '/' . $mark . "_" . microtime(true) . '_' . $i . ".csv";
            $fp = fopen($fileName, "w");
            // 輸出Excel列頭信息
            foreach ($headArr as $key => $value) {
                if ($key == 0) {
                    $headArr[$key] = $bom . $value; // 添加Bom頭解決亂碼問題
                } else {
                    $headArr[$key] = $value;
                }
            }
            fputcsv($fp, $headArr);
            // 保存文件名稱
            $fileNameArr[] = $fileName;
            // 獲取分片數據
            $dataArr = $query->offset($i * $sqlLimit)->limit($sqlLimit)->get()->toArray();
            // 數據處理
            foreach ($dataArr as $item) {
                $item = json_decode(json_encode($item), true);
                $tmpItemArr = []; // 數據編碼處理
                $tmpItemArr['sender_name'] = $bom . $item['sender_name'];
                $tmpItemArr['sender_phone'] = $item['sender_phone'];
                $tmpItemArr['sender_province_name'] = $item['sender_province_name'];
                $tmpItemArr['sender_city_name'] = $item['sender_city_name'];
                $tmpItemArr['sender_county_name'] = $item['sender_county_name'];
                $tmpItemArr['parent_order_number'] = $item['parent_order_number'];
                $tmpItemArr['sender_street_name'] = $item['sender_street_name'];
                $tmpItemArr['sender_detail'] = $item['sender_detail'];
                $tmpItemArr['order_status'] = $item['order_status'];
                $tmpItemArr['express_company_name'] = $item['express_company_name'];
                $tmpItemArr['weight_from_express'] = $item['weight_from_express'];
                $tmpItemArr['freight'] = $item['freight'];
                $tmpItemArr['remark_from_express'] = $item['remark_from_express'];
                $tmpItemArr['order_id'] = $item['order_id'];
                $tmpItemArr['express_number'] = $item['express_number'];
                $tmpItemArr['express_company_code'] = $item['express_company_code'];
                $tmpItemArr['warehouse_name'] = $item['warehouse_name'];
                $tmpItemArr['order_expressesId'] = $item['order_expressesId'];
                $tmpItemArr['waybill_signCreatedAt'] = $item['waybill_signCreatedAt'];
                $tmpItemArr['tracking_number'] = $item['tracking_number'];
                $tmpItemArr['express_signed_at'] = $item['express_signed_at'];
                $cnt++;
                if ($cnt == $sqlLimit) {
                    ob_flush();
                    flush();
                    $cnt = 0;
                }
                fputcsv($fp, $tmpItemArr);
                unset($tmpItemArr);
            }
            fclose($fp);
        }

        // 生成Zip包並導出
        $zip = new \ZipArchive();
        $zipName = $mark . '_' . microtime(true) . '.zip';
        // 檢測Zip文件夾是否存在(按日期生成目錄)
        $zipTmpDir = public_path('Export/ZipTmp/' . date('Ymd'));
        if (!is_dir($zipTmpDir))
            mkdir(iconv("UTF-8", "GBK", $zipTmpDir), 0777, true);
        // 打開壓縮包
        $zip->open($zipTmpDir . '/' . $zipName, ZipArchive::CREATE);
        foreach ($fileNameArr as $fileNameValue) {
            $zip->addFile($fileNameValue, basename($fileNameValue));
        }
        $zip->close();
        // 刪除Excel臨時文件
        foreach ($fileNameArr as $fileNameValue) {
            unlink($fileNameValue);
        }
        header('Cache-Control: max-age=0');
        header('Content-Description: File Transfer');
        header('Content-Disposition: attachment;filename=' . $zipName);
        header('Content-type: application/zip');
        header('Content-Transfer-Encoding: binary');
        header('Content-Length: ' . filesize($zipTmpDir . '/' . $zipName));
        @readfile($zipTmpDir . '/' . $zipName);
        unlink($zipTmpDir . '/' . $zipName);
        Log::info("截止時間:" . date('Y-m-d H:i:s', time()));
    }

    /**
     * 導出CSV(分片查詢寫入文件)
     */
    public function indexSix()
    {
        set_time_limit(0);
        Log::info("起始時間:" . date('Y-m-d H:i:s', time()));
        // 保存目錄
        $dir_path = public_path('Six');
        if (!is_dir($dir_path))
            mkdir($dir_path, 0777, true);
        // 文件名
        $fileName = microtime(true) . '.csv';
        //  檢測文件是否已存在
        if (file_exists($dir_path . '/' . $fileName))
            unlink($dir_path . '/' . $fileName);
        // 打開文件
        $myfile = fopen($dir_path . '/' . $fileName, "w") or die("Unable to open file!");
        // Excel列表頭
        $head = "\""
            . '編號1' . "\",\""
            . '編號2' . "\",\""
            . '編號3' . "\",\""
            . '編號4' . "\",\""
            . '編號5' . "\",\""
            . '編號6' . "\",\""
            . '編號7' . "\",\""
            . '編號8' . "\",\""
            . '編號9' . "\",\""
            . '編號10' . "\",\""
            . '編號11' . "\",\""
            . '編號12' . "\",\""
            . '編號13'
            . "\"\n";
        $head = mb_convert_encoding($head, 'GBK', 'utf-8');
        // 寫入頭部信息
        fwrite($myfile, $head);
        // 寫入內容
        $select = [
            "orders.*",
            "order_senders.sender_name",
            "order_senders.sender_phone",
            "order_senders.sender_province_name",
            "order_senders.sender_city_name",
            "order_senders.sender_county_name",
            "po.order_number as parent_order_number",
            "order_senders.sender_street_name",
            "order_senders.sender_detail",
            "orders.order_status",
            "order_expresses.express_company_name",
            "order_expresses.weight_from_express",
            "order_expresses.freight",
            "order_expresses.remark_from_express",
            "order_senders.order_id",
            "order_expresses.express_number",
            "order_expresses.express_company_code",
            "warehouses.warehouse_name",
            "order_expresses.id as order_expressesId",
            DB::raw("( SELECT GROUP_CONCAT( order_waybills.warehouse_signed_at ) FROM order_waybills WHERE order_expresses.id = order_waybills.order_express_id ) AS waybill_signCreatedAt"),
            DB::raw("( SELECT GROUP_CONCAT( order_waybills.tracking_number ) FROM order_waybills WHERE order_expresses.id = order_waybills.order_express_id ) AS tracking_number"),
            DB::raw("( SELECT GROUP_CONCAT( order_waybills.express_signed_at ) FROM order_waybills WHERE order_expresses.id = order_waybills.order_express_id) AS express_signed_at"),
        ];
        $query = DB::table('orders')
            ->leftJoin('order_expresses', 'orders.id', '=', 'order_expresses.order_id')
            ->leftJoin('order_senders', 'orders.id', '=', 'order_senders.order_id')
            ->leftJoin('orders as po', 'po.id', '=', 'orders.parent_id')
            ->leftJoin('order_receivers', 'orders.id', '=', 'order_receivers.order_id')
            ->leftJoin('warehouses', 'order_receivers.warehouse_code', '=', 'warehouses.warehouse_code')
            ->orderBy('orders.created_at', 'desc');

        $query->where('orders.created_at', '>', '2019-1-23')
              ->where('orders.created_at', '<', '2019-4-11')
              ->select($select)->chunk(20000, function ($terminal) use (&$myfile) {
                foreach ($terminal as $key => $value) {
                    $txt = "\""
                        . $value->sender_name . "\t\",\""
                        . $value->sender_phone . "\t\",\""
                        . $value->sender_province_name . "\t\",\""
                        . $value->sender_city_name . "\t\",\""
                        . $value->sender_county_name . "\t\",\""
                        . $value->parent_order_number . "\t\",\""
                        . $value->sender_street_name . "\t\",\""
                        . $value->sender_detail . "\t\",\""
                        . $value->order_status . "\t\",\""
                        . $value->express_company_name . "\t\",\""
                        . $value->weight_from_express . "\t\",\""
                        . $value->freight . "\t\",\""
                        . $value->remark_from_express . "\t\",\""
                        . $value->order_id . "\t\",\""
                        . $value->express_number . "\t\",\""
                        . $value->express_company_code . "\t\",\""
                        . $value->warehouse_name . "\t\",\""
                        . $value->order_expressesId . "\t\",\""
                        . $value->waybill_signCreatedAt . "\t\",\""
                        . $value->tracking_number . "\t\",\""
                        . $value->express_signed_at . "\t\",\""
                        . "\t\"\n";
                    $txt = mb_convert_encoding($txt, 'GBK', 'utf-8');
                    fwrite($myfile, $txt);
                    unset($txt);
                }
            });

        fclose($myfile);
        // 瀏覽器下載
        header('Cache-Control: max-age=0');
        header('Content-Description: File Transfer');
        header('Content-Disposition: attachment;filename=' . $fileName);
        header('Content-type: application/vnd.ms-excel');
        header('Content-Transfer-Encoding: binary');
        header('Content-Length: ' . filesize($dir_path . '/' . $fileName));
        @readfile($dir_path . '/' . $fileName);
        // 刪除臨時文件
        unlink($dir_path . '/' . $fileName);

        Log::info("截止時間:" . date('Y-m-d H:i:s', time()));
    }


}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章