首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 記憶體耗盡錯誤解決:memory_limit 調整與 unset 釋放最佳實踐

     編輯:PHP 綜合知識

處理大檔案(如 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() 讀入陣列是主因

二、臨時方案:調整 memory_limit

; php.ini
memory_limit = 256M ; 或 512M

; 或執行時修改(需未被 disable_functions 限制)
ini_set('memory_limit', '512M');

缺點: 只能延緩崩潰,資料量增長時同樣會 OOM(Out Of Memory)。

三、根治方案:流式處理 + unset 釋放

錯誤代碼(一次性讀入)

<?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");
?>

四、記憶體管理黃金法則

  1. 避免 file()、file_get_contents() 讀大檔 → 改用 fopen() + fgets() 逐行
  2. 大陣列分批處理:array_chunk($bigArray, 1000) 循環處理,每批次後 unset($chunk)
  3. 及時 unset 大變數:unset($bigArray, $bigString, $resource)
  4. 生成器 替代陣列:yield 惰性產出,常駐記憶體極低
  5. 資源關閉:fclose()、curl_close()、mysqli_close()、pdo = null
  6. 監控記憶體:memory_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 依賴管理記憶體優化

# composer.json
"config": {
    "platform": {"php": "8.1"},
    "memory-limit": 1024  # Composer 內部限制
}

# 或執行時
COMPOSER_MEMORY_LIMIT=-1 composer install  # -1 為無限制

核心心法:「串流處理、分批釋放、生成器優先」。記憶體問題 90% 源於「一次性載入」,改為「邊讀邊處理」即可從根本解決。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved