PHP+MySQL導出大量數據(Iterator yield)

開發中常常遇到這樣的場景

產品汪:我要在後臺作一個功能,能夠導出自定義時間範圍的訂單信息。開發小哥二話不說,半天就把功能作完並上線了。結果,次日一上班產品汪過來就是拍桌子: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

相關文章
相關標籤/搜索