解決 PHP 網站伺服器遇到 Allowed memory exhausted 問題除了常見的調整記憶體上限值方式,其他解決方式:(1) CSV 或 TXT 文字檔案類型,避免使用讀取檔案全部內容的函數、(2) JSON 大檔案可以使用 jq 拆解成小檔案、(3) 資料庫查詢使用 LIMIT、OFFSET 語法等方式。
ElePHPants by Noriko YAMAMOTO 使用創用 CC BY 授權 |
問題狀況
網站伺服器顯示記憶體耗盡的錯誤訊息
PHP Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 20480 bytes)
問題原因
解決方案
- 修改記憶體上 memory_limit 詳見 php - 解決 Allowed memory size of 134217728 bytes exhausted 問題 @ Life Is Struggle :: 隨意窩 Xuite日誌
- 減少載入的 Library,例如關閉 Laravel 啟動時不需要載入的 providers 服務
狀況 1. 處理 CSV, TXT 文字檔案類型
如果檔案較大,應避免使用一次讀取全部檔案內容的 PHP file_get_contents、file_put_contents 等函數。讀取檔案建議改成使用 Generators 或 fgets 逐行讀取檔案內容,減少記憶體的使用。而寫入檔案則可使用 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 是 0 (對應到 `id` 欄位值 1 的紀錄),筆數 row_count 為 5。LIMIT offset, row_count 改寫成 LIMIT 0, 5
SELECT * FROM `cats` ORDER BY `id` LIMIT 0, 5
步驟 (2) 再處理第二頁 5 筆紀錄:第二頁第一筆 offset 是 5 (對應到 `id` 欄位值 6 的紀錄),筆數 row_count 為 5。LIMIT offset, row_count 改寫成 LIMIT 5, 5。
減少查詢結果返回記錄筆數,達到減少記憶體使用量。SELECT * FROM `cats` ORDER BY `id` LIMIT 5, 5
狀況 4. 其他狀況
參考資料
- php - 解決 Allowed memory size of 134217728 bytes exhausted 問題 @ Life Is Struggle :: 隨意窩 Xuite日誌
- jq Manual (development version)
- MySQL :: MySQL 8.0 Reference Manual :: 13.2.10 SELECT Statement
- Fixing PHP Fatal error: Allowed memory size of (X) bytes exhausted.
- 貓 - 維基百科,自由的百科全書
附錄
建立貓咪資料表的範例資料語法
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', '加州閃亮貓')
;
留言
張貼留言