這篇文章介紹的內容是關於php csv 匯出,有著一定的參考價值,現在分享給大家,有需要的朋友可以參考一下
概述:
最近公司項目要求把資料除了頁面輸出也希望有匯出功能,雖然之前也做過幾個匯出功能,但這次資料量相對比較大,差不多一天資料就20W條,要求導7天或者30天,那麼資料量就輕鬆破百萬了甚至破千萬,因此開發的過程中發現了一些大資料匯出的坑,在此跟大家分享一下,互相學習。
準備:
1、PHP設定坑:
此配置一般PHP預設是30秒,如果你是資料小的,可能就不會發現有該設定問題,但如果你資料達到了百萬級匯出,往往30秒是不夠的,因此你需要在你的指令碼中添加 set_time_limit(0),讓該指令碼沒有執行時間現在
此配置一般php預設是128M,如果之前做過小資料的朋友可能也會動過這個配置就能解決許多問題,或許有人想,你大資料也把這個調大不就行了嗎?那麼真的是too young too native了,你本地能設定1G或者無限制或許真的沒問題,但是正式場,你這麼搞遲早會出事的,一個PHP程式占那麼大的記憶體的空間,如果你叫你公司營運幫忙調一下配置,估計營運一定很不情願,伺服器硬體這麼搞也是太奢侈了。所以說,我們要盡量避免調大該設定。
2、excel坑:
既然是匯出資料,大夥們當然馬上想到了excel格式了,多方便查看資料呀,然而萬萬沒想到excel也是有脾氣的呀!
Excel 2003及以下的版本。一張表最大支援65536行資料,256列。Excel 2007-2010版本。一張表最大支援1048576行,16384列。
也就是說你想幾百萬條輕輕鬆鬆一次性匯入一張EXCEL表是不行的,你起碼需要進行資料分割,保證資料不能超過104W一張表。
既然資料限制在104W,那麼資料分割就資料分割唄,於是你嘗試50W一次匯入表,然而PHPexcel內部有函數報記憶體溢出錯誤,然後你就不斷的調小資料量,直到5W一次匯入你都會發現有記憶體溢出錯誤。這是為什麼呢,雖然你分割資料來匯入多個資料表,但是最後PHPexcel內部還是一次性把所有表資料放進一個變數中來建立檔案……額,這幾百萬資料一個變數儲存,你想記憶體不溢出,還真有點困難。
(後來看了一些文章發現PHPExcel也有解決方案,PHPExcel_Settings::setCacheStorageMethod方法更改緩衝方式來減小記憶體的使用)
3、csv坑:
EXCEL這麼麻煩,我不用還不行嗎?我用csv檔案儲存,既不限制數量,還能直接用EXCEL來查看,又能以後把檔案匯入資料庫,一舉幾得豈不是美哉?咦,少俠好想法!但是CSV也有坑哦!
當你用PHP原生函數putcsv()其實就使用到了輸出緩衝buffer,如果你把幾百萬的資料一直用這個函數輸出,會導致輸出緩衝太大而報錯的,因此我們每隔一定量的時候,必須進行將輸出緩衝中的內容取出來,設定為等待輸出狀態。具體操作是:
ob_flush();flush();
具體說明介紹:PHP flush()與ob_flush()的區別詳解
大多數人看csv檔案都是直接用EXCEL開啟的。額,這不就是回到EXCEL坑中了嗎?EXCEL有資料顯示限制呀,你幾百萬資料只給你看104W而已。什嗎?你不管?那是他們開啟檔案不對而已?不好不好,我們解決也不難呀,我們也把資料分割一下就好了,再分開csv檔案儲存,反正你不分割資料變數也會記憶體溢出。
4、總結做法
分析完上面那些坑,那麼我們的解決方案來了,假設資料量是幾百萬。
1、那麼我們要從資料庫中讀取要進行資料量分批讀取,以防變數記憶體溢出,
2、我們選擇資料儲存檔案格式是csv檔案,以方便匯出之後的閱讀、匯入資料庫等操作。
3、以防不方便excel讀取csv檔案,我們需要104W之前就得把資料分割進行多個csv檔案儲存
4、多個csv檔案輸出給使用者下載是不友好的,我們還需要把多個csv檔案進行壓縮,最後提供給一個ZIP格式的壓縮包給使用者下載就好。
代碼:
//匯出說明:因為EXCEL單表只能顯示104W資料,同時使用PHPEXCEL容易因為資料量太大而導致佔用記憶體過大, //因此,資料的輸出用csv檔案的格式輸出,但是csv檔案用EXCEL軟體讀取同樣會存在只能顯示104W的情況,所以將資料分割儲存在多個csv檔案中,並且最後壓縮成zip檔案提供下載 function putCsv(array $head, $data, $mark = 'attack_ip_info', $fileName = "test.csv") { set_time_limit(0); $sqlCount = $data->count(); // 輸出Excel檔案頭,可把user.csv換成你要的檔案名稱 header('Content-Type: application/vnd.ms-excel;charset=utf-8'); header('Content-Disposition: attachment;filename="' . $fileName . '"'); header('Cache-Control: max-age=0'); $sqlLimit = 100000;//每次只從資料庫取100000條以防變數緩衝太大 // 每隔$limit行,重新整理一下輸出buffer,不要太大,也不要太小 $limit = 100000; // buffer計數器 $cnt = 0; $fileNameArr = array(); // 逐行取出資料,不浪費記憶體 for ($i = 0; $i < ceil($sqlCount / $sqlLimit); $i++) { $fp = fopen($mark . '_' . $i . '.csv', 'w'); //產生臨時檔案 // chmod('attack_ip_info_' . $i . '.csv',777);//修改可執行許可權 $fileNameArr[] = $mark . '_' . $i . '.csv'; // 將資料通過fputcsv寫到檔案控制代碼 fputcsv($fp, $head); $dataArr = $data->offset($i * $sqlLimit)->limit($sqlLimit)->get()->toArray(); foreach ($dataArr as $a) { $cnt++; if ($limit == $cnt) { //重新整理一下輸出buffer,防止由於資料過多造成問題 ob_flush(); flush(); $cnt = 0; } fputcsv($fp, $a); } fclose($fp); //每產生一個檔案關閉 } //進行多個檔案壓縮 $zip = new ZipArchive(); $filename = $mark . ".zip"; $zip->open($filename, ZipArchive::CREATE); //開啟壓縮包 foreach ($fileNameArr as $file) { $zip->addFile($file, basename($file)); //向壓縮包中添加檔案 } $zip->close(); //關閉壓縮包 foreach ($fileNameArr as $file) { unlink($file); //刪除csv臨時檔案 } //輸出壓縮檔提供下載 header("Cache-Control: max-age=0"); header("Content-Description: File Transfer"); header('Content-disposition: attachment; filename=' . basename($filename)); // 檔案名稱 header("Content-Type: application/zip"); // zip格式的 header("Content-Transfer-Encoding: binary"); // header('Content-Length: ' . filesize($filename)); // @readfile($filename);//輸出檔案; unlink($filename); //刪除壓縮包臨時檔案 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
總結:
其實上面代碼還是有最佳化的空間的,比如說用異常捕捉,以防因為某些錯誤而導致產生了一些臨時檔案又沒有正常刪除,還有PHPexcel的緩衝設定也許能解決記憶體溢出問題,可以產生一個EXCEL檔案多個工作表的形式,這樣對於檔案閱讀者來說更友好。
以上便是本人對PHP大資料匯出的見解,希望能幫到您們,同時不足的地方請多多指教!
————————————————————————————————————
2017年12月17日
PS:最近瞭解其實關於記憶體溢出的問題,用迭代器來處理會方便多了。
著作權聲明:每一篇原創文章都是我的心血,歡迎轉載,但請轉載前留個評論,感謝您的支援!!! https://blog.csdn.net/Tim_phper/article/details/77581071