解決網站伺服器遇到 Allowed memory exhausted 問題

解決 PHP 網站伺服器遇到 Allowed memory exhausted 問題除了常見的調整記憶體上限值方式,其他解決方式:(1) CSV 或 TXT 文字檔案類型,避免使用讀取檔案全部內容的函數、(2) JSON 大檔案可以使用 jq 拆解成小檔案、(3) 資料庫查詢使用 LIMIT、OFFSET 語法等方式。

ElePHPants by Noriko YAMAMOTO
ElePHPants by Noriko YAMAMOTO 使用創用 CC BY 授權


問題狀況

網站伺服器顯示記憶體耗盡的錯誤訊息

PHP Fatal error:  Allowed memory size of 134217728 bytes exhausted (tried to allocate 20480 bytes)

問題原因

網站應用程式使用過多系統記憶體而造成的錯誤

解決方案

常見的解決方式有
其他調整 PHP 程式碼的方式

狀況 1. 處理 CSV, TXT 文字檔案類型

如果檔案較大,應避免使用一次讀取全部檔案內容的 PHP file_get_contentsfile_put_contents 等函數。讀取檔案建議改成使用 Generatorsfgets 逐行讀取檔案內容,減少記憶體的使用。而寫入檔案則可使用 fwrite 附加檔案 (append) 方式寫入。


狀況 2. 處理 JSON 檔案類型

處理過看起來檔案看起來不大的 250 MB 的 JSON 檔案,但是內容有六百多萬行。原始資料無法透過逐行讀取檔案方式處理,改成使用 jq 將快三千多筆紀錄,拆成三千多個小檔案,再做後續處理。

以下是簡化過的 JSON 檔案內容 (取自 JSON Example),檔名 example.json

{"menu": {
    "header": "SVG Viewer",
    "items": [
        {   
            "id": "OpenNew", 
            "label": "Open New"
        },
        null,
        {
            "id": "ZoomIn", 
            "label": "Zoom In"
        }
    ]
}}

步驟 1: 安裝 jq - 處理 JSON 檔案的命令列工具

前往 jq 網站,根據作業系統版本,安裝對應的 jq。支援的作業系統有 Windows, Linux 與 OSX。根據個人經驗,Windows 版本 jq 有奇怪問題,個人偏好透過 Cygwin 安裝 jq package。


步驟 2: 瞭解 JSON 檔案結構

檔案小時,可以很快看到需要處理的資料樹路徑是 menu/items 。檔案大時,開啟檔案可能需要等待一段時間甚至當掉無法打開。可以透過指令看部分檔案內容,例如:

看檔案前 10 行內容

head -n 行數 檔名
head -n 10 example.json

或者是逐頁看檔案內容

cat 檔名 | more
cat example.json | more  

按 space 按鍵換頁,再按 q 就可以離開逐頁觀看模式


步驟 3: 計算記錄筆數

jq '結構樹 | length' 檔名
jq '.menu.items | length' example.json

範例檔案的結果是 3 


步驟 4: 預覽第一筆記錄

第一筆從 0 開始,結構樹語法需要標記 [0

jq '結構樹' 檔名
jq '.menu.items[0]' example.json

範例檔案的結果是

{
  "id": "OpenNew",
  "label": "Open New"
}


步驟 5: 逐筆拆解紀錄,另存檔案

根據步驟 2 取得的記錄筆數使用程式產生多組指令

jq '結構樹' 檔名 > 輸出檔名
jq '.menu.items[0]' example.json > 0.json
jq '.menu.items[1]' example.json > 1.json
jq '.menu.items[2]' example.json > 2.json

後續就很容易解析小檔案內容。


狀況 3. 處理資料庫過多筆資料

資料庫查詢直接 SELECT 超過十萬、百筆筆的紀錄,遭遇到記憶體錯誤。解決方式則是查詢時加上 OFFSET 及 LIMIT 條件,逐頁分批地的方式處理資料。

查詢語法

SELECT * FROM `資料表名稱` ORDER BY `欄位名稱` LIMIT offset, row_count

範例資料表總共有 10 筆紀錄 [線上操作資料庫]


步驟 (1) 先處理第一頁前 5 筆紀錄:資料表 `cats` 欄位 `id` 欄位值由小至大,offset 從 0 開始,所以第一筆 offset 是 (對應到 `id` 欄位值 1 的紀錄),筆數 row_count 為 5。LIMIT offset, row_count 改寫成 LIMIT 0, 5

SELECT * FROM `cats` ORDER BY `id` LIMIT 0, 5


步驟 (2) 再處理第二頁 5 筆紀錄:第二頁第一筆 offset 是 (對應到 `id` 欄位值 6 的紀錄),筆數 row_count 為 5。LIMIT offset, row_count 改寫成 LIMIT 5, 5。

SELECT * FROM `cats` ORDER BY `id` LIMIT 5, 5

減少查詢結果返回記錄筆數,達到減少記憶體使用量。


狀況 4. 其他狀況

Fixing PHP Fatal error: Allowed memory size of (X) bytes exhausted. 文章中提到原因還有使用太大的陣列或過多的迴圈、大陣列的巢狀遞迴迴圈。修改記憶體上 memory_limit 只是治標方法,需要回頭檢視程式碼,搭配 memory profiling 工具調整演算邏輯。


參考資料

附錄

建立貓咪資料表的範例資料語法 

CREATE TABLE IF NOT EXISTS `cats` (

  `id` int(6) unsigned NOT NULL,

  `content` varchar(200) NOT NULL,

  PRIMARY KEY (`id`)

) DEFAULT CHARSET=utf8;

INSERT INTO `cats` (`id`, `content`) VALUES

  ('1', '阿比西尼亞貓'),

  ('2', '美國卷耳貓'),

  ('3', '美國短毛貓'),

  ('4', '峇里貓'),

  ('5', '巴西短毛貓'),

  ('6', '英國短毛貓'),

  ('7', '伯曼貓'),

  ('8', '孟買貓'),

  ('9', '緬甸貓'),

  ('10', '加州閃亮貓')

  ;


留言