處理大檔案(如 580MB 日誌、200 萬行)時常遇到 Allowed memory size exhausted。單純調大 memory_limit 治標不治本,正確做法是「流式處理 + 及時釋放」。本文以實戰案例示範完整解決方案。
Fatal error: Allowed memory size of 134217728 bytes exhausted
(tried to allocate 2611816 bytes) in /path/to/script.php on line 42
134217728 bytes = 128MB(預設 memory_limit)file() 讀入陣列是主因; php.ini
memory_limit = 256M ; 或 512M
; 或執行時修改(需未被 disable_functions 限制)
ini_set('memory_limit', '512M');
缺點: 只能延緩崩潰,資料量增長時同樣會 OOM(Out Of Memory)。
<?php
$row = file("$v"); // 整個檔案讀入陣列,佔用大量記憶體
foreach ($row as $key => $value) {
// 處理邏輯
}
?>
<?php
function processLargeLog($directory, $outputFile) {
$files = scandir($directory);
$logFiles = [];
// 1. 篩選 .log 檔案
foreach ($files as $v) {
if (preg_match('/^mail\.log/', $v)) {
$logFiles[] = $v;
}
}
// 2. 逐檔處理
$outHandle = fopen($outputFile, 'a');
foreach ($logFiles as $logFile) {
$filePath = $directory . '/' . $logFile;
echo "處理檔案: $filePath\n";
$handle = fopen($filePath, 'r');
if (!$handle) continue;
// 3. 逐行讀取,不載入整個檔案
while (($line = fgets($handle)) !== false) {
// 匹配失敗特徵
if (preg_match('/host name lookup failure|Connection timed out|Connection refused|cannot find your reverse hostname/', $line)) {
// 提取 Email
if (preg_match('/\b[\w.+-]+@[\w.-]+\.\w{2,}\b/', $line, $matches)) {
$email = trim($matches[0]);
fwrite($outHandle, $email . "\n");
echo "記錄失敗郵箱: $email\n";
}
}
}
fclose($handle); // 關鍵:釋放檔案控制代碼
unset($line); // 釋放變數(雖然下輪會覆寫,但習慣性釋放)
}
fclose($outHandle);
echo "處理完成,結果儲存於: $outputFile\n";
}
// 使用
set_time_limit(1800); // 30 分鐘
processLargeLog(".", "failed_emails.txt");
?>
fopen() + fgets() 逐行array_chunk($bigArray, 1000) 循環處理,每批次後 unset($chunk)unset($bigArray, $bigString, $resource)yield 惰性產出,常駐記憶體極低fclose()、curl_close()、mysqli_close()、pdo = nullmemory_get_usage(true)、memory_get_peak_usage(true)<?php
function readLogLines($filePath) {
$handle = fopen($filePath, 'r');
try {
while (($line = fgets($handle)) !== false) {
yield $line;
}
} finally {
fclose($handle);
}
}
function extractFailedEmails($directory) {
$files = glob("$directory/mail.log*");
foreach ($files as $file) {
foreach (readLogLines($file) as $line) {
if (preg_match('/host name lookup failure|Connection timed out|Connection refused|cannot find your reverse hostname/', $line)) {
if (preg_match('/\b[\w.+-]+@[\w.-]+\.\w{2,}\b/', $line, $m)) {
yield trim($m[0]);
}
}
}
}
}
// 使用:邊產出邊寫入,記憶體恆定
$out = fopen('failed_emails.txt', 'a');
foreach (extractFailedEmails('.') as $email) {
fwrite($out, $email . "\n");
}
fclose($out);
?>
| 方法 | 峰值記憶體 | 處理 580MB 日誌 |
|---|---|---|
| file() 讀入陣列 | > 1GB (OOM) | 失敗 |
| fgets() 逐行 + unset | ~2-5 MB | 成功 |
| Generator yield | ~1-2 MB | 成功(最優) |
# composer.json
"config": {
"platform": {"php": "8.1"},
"memory-limit": 1024 # Composer 內部限制
}
# 或執行時
COMPOSER_MEMORY_LIMIT=-1 composer install # -1 為無限制
核心心法:「串流處理、分批釋放、生成器優先」。記憶體問題 90% 源於「一次性載入」,改為「邊讀邊處理」即可從根本解決。