PHPメモリオーバーフローについて考える


背景
最近大量のデータの書き出しやデータの読み込みをしていると、PHPメモリのオーバーフローの問題に遭遇することが多く、問題を解決した後、いくつかの経験をまとめ、文章にまとめて記録しています.
さいてきかてん
  • SQL文を最適化し、遅いクエリーを避け、合理的にインデックスを確立し、指定したフィールドをクエリーし、sql最適化はここでは展開されません.
  • クエリの結果セットが大きなオブジェクトである場合の配列変換処理は、LaravelにtoArray()があり、Yii 2にasArray()があるなど、フレームワークで一般的に方法があります.
  • 大きな配列に対してデータの切断処理を行って、PHP関数はarray_がありますchunk()、array_slice().
  • は、大規模な文字列およびオブジェクトに対して、参照伝達&を使用します.
  • で使用した変数はタイムリーにunsetされます.
  • でエクスポートされたファイルフォーマットはexcelからcsv
  • に変更されました.
  • ini_set('memory_limit',')は、プログラムが使用できるメモリを設定します(推奨しません).

  • 考える
    メモリ管理
    PHPのメモリはどのように管理されていますか?C言語を学ぶには、開発者が手動でメモリを管理する必要があります.PHPでは、Zendエンジンは、要求された関連データを処理するための特別なメモリマネージャを提供する.要求関連データは、単一の要求にサービスするだけで、遅くとも要求の終了時にデータを解放します.
    上図は公式サイトからの説明のスクリーンショットです
    メモリの漏洩を防止し、すべてのメモリをできるだけ早く解放することは、メモリ管理の重要な構成部分です.セキュリティ上の理由により、Zendエンジンは、上述したAPIロック割り当てのメモリをすべて解放します.
    ごみ回収メカニズム
    簡単に言えば、
    PHP5.3までは、参照カウントで管理していました.PHPの変数がzvalの変数コンテナに存在し、変数が参照されている場合、参照カウント+1、変数参照カウントが0の場合、PHPはメモリにこの変数を破棄します.ただし、リファレンスカウントループリファレンスの場合、リファレンスカウントは0に減少せず、メモリが漏洩します.
    PHP5.3以降は最適化され、リファレンスカウントが減少するたびにリサイクルサイクルに入るわけではありません.ルートバッファが満額になってからゴミ回収が開始されます.これにより、リサイクルリファレンスの問題を解決したり、総メモリリークを1つのしきい値の下に維持したりすることができます.
    コード#コード#
    phpexcelを使用するとメモリオーバーフローが頻繁に発生するため、csvファイルを生成するコードを共有します.
    params['excelSavePath'];
    
            foreach (array_chunk($data, 10000) as $key => $value) {
                self::$outPutFile = '';
                $subject          = !empty($fileName) ? $fileName : 'data_';
                $subject          .= date('YmdHis');
                if (empty($value) || empty($formFields)) {
                    continue;
                }
    
                self::$outPutFile = $tmpPath . $subject . $key . '.csv';
                if (!file_exists(self::$outPutFile)) {
                    touch(self::$outPutFile);
                }
                $index  = array_keys($formFields);
                $header = array_values($formFields);
                self::outPut($header);
    
                foreach ($value as $k => $v) {
                    $tmpData = [];
                    foreach ($index as $item) {
                        $tmpData[] = isset($v[$item]) ? $v[$item] : '';
                    }
                    self::outPut($tmpData);
                }
                $fileArr[] = self::$outPutFile;
            }
            
            $zipFile = $tmpPath . $fileName . date('YmdHi') . '.zip';
            $zipRes = self::zipFile($fileArr, $zipFile);
            return $zipRes;
        }
    
        /**
         *        
         * @param array $data
         */
        public static function outPut($data = [])
        {
            if (is_array($data) && !empty($data)) {
                $data = implode(',', $data);
                file_put_contents(self::$outPutFile, iconv("UTF-8", "GB2312//IGNORE", $data) . PHP_EOL, FILE_APPEND);
            }
        }
    
        /**
         *     
         * @param $sourceFile
         * @param $distFile
         * @return mixed
         */
        public static function zipFile($sourceFile, $distFile)
        {
            $zip = new \ZipArchive();
            if ($zip->open($distFile, \ZipArchive::CREATE) !== true) {
                return $sourceFile;
            }
    
            $zip->open($distFile, \ZipArchive::CREATE);
            foreach ($sourceFile as $file) {
                $fileContent = file_get_contents($file);
                $file        = iconv('utf-8', 'GBK', basename($file));
                $zip->addFromString($file, $fileContent);
            }
            $zip->close();
            return $distFile;
        }
        
            /**
         *     
         * @param $filePath
         * @param $fileName
         */
        public static function download($filePath, $fileName)
        {
            if (!file_exists($filePath . $fileName)) {
                header('HTTP/1.1 404 NOT FOUND');
            } else {
                //             
                $file = fopen($filePath . $fileName, "rb");
    
                //                 
                Header("Content-type: application/octet-stream");
                //         
                Header("Accept-Ranges: bytes");
                //Content-Length                   
                Header("Accept-Length: " . filesize($filePath . $fileName));
                //       ,            ,         $file_name     
                Header("Content-Disposition: attachment; filename=" . $fileName);
    
                //               
                echo fread($file, filesize($filePath . $fileName));
                fclose($file);
                exit();
            }
        }
    }            
    

    呼び出し先コード
    $fileName = "      ";
    $stockRes = []; //      
    $formFields = [
        'store_id'  => '  ID',
        'storeName' => '    ',
        'sku'       => 'SKU  ',
        'name'      => 'SKU  ',
        'stock'     => '  ',
        'reason'    => '  '
    ];
    $fileRes    = ExportService::exportData($fileName, $stockRes, $formFields);
    $tmpPath    = \Yii::$app->params['excelSavePath']; //     
    $fileName   = str_replace($tmpPath, '', $fileRes);
    
    //     
    ExportService::download($tmpPath, $fileName);
    
    
    

    原文住所:https://tsmliyun.github.io/20...交流を歓迎します.間違いがあれば、指摘してください.ありがとうございます.