產品汪:我要在後臺作一個功能,能夠導出自定義時間範圍的訂單信息。開發小哥二話不說,半天就把功能作完並上線了。結果,次日一上班產品汪過來就是拍桌子:MD,我想把去年一全年的訂單都導出來,結果後臺直接就掛了!php
開發小哥一查,原來是內存溢出了,一年下來的的訂單量足足有1000W條。因而,開發小哥跟產品汪吵了起來:你TM色不色傻,1000W的數據你導出來幹diao,你是否是想把服務器給搞掛掉?mysql
因而,產品汪與程序狗就這麼結下樑子了。sql
可是。產品的需求你敢這樣懟回去,那若是是老闆提的這個需求呢,就是硬要把1000W條記錄導出來,你還不得乖乖回去碼代碼?數組
那麼,這個若是真要導出這大量數據,該怎麼作呢?開發中咱們常常會使用框架來提升咱們的開發效率,但也意味着框架會對一些數據進行封裝。服務器
好比Yii2,當咱們想獲取去年一年的訂單時,咱們的代碼會這樣寫:框架
Order::find()->where("create_time between '2016-01-01' AND '2016-12-31'")->all();大數據
當咱們將上面代碼獲得的結果集再拿去遍歷時,數據量一大,就會內存溢出。
緣由是:Yii2中,對all方法進行了封將,將大量的數據存入了數組中,而遍歷大數據,必然會致使內存迅速上升。
那如何取出大量數據,而又不存到數組中呢?這就要用到了PHP中的迭代器:Iterator。若是有看過PDO::query的返回值類型的話,咱們會發現,這個方法返回的PDOStatement,正是對Iterator的實現。關於Iterator,請自行腦補。code
即然框架幫咱們作了多餘的封裝,那麼咱們就改用原生API來實現。如下是完整代碼內存
$sql = 'select * from user'; $pdo = new\PDO( 'mysql:host=127.0.0.1;dbname=test', 'root', 'root' ); $pdo->setAttribute( \PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false); $rows = $pdo->query($sql); $filename = date('Ymd').'.csv';//設置文件名 header('Content-Type:text/csv'); header("Content-Disposition:attachment;filename={$filename}"); $out = fopen('php://output','w'); fputcsv( $out, [ 'id', 'username', 'password', 'create_time' ] ); foreach( $rows as $row ){ $line=[ $row['id'], $row['username'], $row['password'], $row['create_time'] ]; fputcsv($out,$line); } fclose($out); $memory=round((memory_get_usage()-$startMemory)/1024/1024,3).'M'.PHP_EOL; file_put_contents('/tmp/test.txt',$memory,FILE_APPEND);
在表中生成7位數量級的記錄,執行上面的代碼,經過查看內存使用,發現整個過程只佔用了0.XM的內存,徹底沒有任何內存溢出的現象。pdo