PHP 連接 MySQL 時,預設使用緩衝查詢:一次性將結果集從 MySQL 傳輸至 PHP 進程記憶體。對於大結果集,這會導致 PHP 記憶體飆升甚至 OOM。非緩衝查詢則逐行從 MySQL 服務器拉取,記憶體占用極低,但限制操作靈活性。本文深度解析兩者機制、API 對應、適用場景與實戰選型建議。
| 特性 | 緩衝查詢(預設) | 非緩衝查詢 |
|---|---|---|
| 結果集存放 | PHP 進程記憶體 | MySQL 服務器,逐行拉取 |
| 記憶體佔用 | 高(結果集大小) | 極低(單行) |
| 指針移動 | 隨意(data_seek) | 僅向前(fetch_row) |
| 並發查詢 | 同一連接可並發 | 必須讀完/釋放才能發送新查詢 |
| 行數統計 | num_rows 即時可用 | 需先 fetch_all 或遍歷計數 |
| 適用場景 | 小/中結果集、需隨機訪問 | 大結果集、串流處理、記憶體受限 |
<?php
/* ---------- mysqli ---------- */
// 緩衝(預設)
$mysqli->query("SELECT * FROM big_table"); // MYSQLI_STORE_RESULT
$mysqli->query("SELECT * FROM big_table", MYSQLI_STORE_RESULT);
$stmt = $mysqli->prepare("SELECT * FROM big_table");
$stmt->execute();
$stmt->store_result(); // 顯式緩衝
// 非緩衝
$mysqli->query("SELECT * FROM big_table", MYSQLI_USE_RESULT);
$mysqli->use_result();
$stmt = $mysqli->prepare("SELECT * FROM big_table");
$stmt->execute(); // 預處理語句預設非緩衝
/* ---------- PDO ---------- */
// 緩衝(預設)
$pdo->query("SELECT * FROM big_table");
// 非緩衝
$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
$pdo->query("SELECT * FROM big_table");
?>
memory_limit,直到賦值給 PHP 變量memory_limit,監控更準確<?php
$stmt = $mysqli->prepare("SELECT * FROM logs WHERE date > ?");
$stmt->bind_param('s', $date);
$stmt->execute();
// 預處理語句預設「非緩衝」,結果留在 MySQL 服務器
// 若需 num_rows 或隨機訪問,必須顯式緩衝:
$stmt->store_result(); // 將全部結果傳回 PHP
echo $stmt->num_rows; // 現在可用
// 用完必須釋放
$stmt->free_result();
// mysqlnd 環境下,更推薦 get_result + fetch_all
$result = $stmt->get_result(); // 取得 mysqli_result
$rows = $result->fetch_all(MYSQLI_ASSOC); // 一次性取陣列
?>
結果集 < 10 萬行 且 需隨機訪問/統計行數
→ 緩衝查詢(預設,最省事)
結果集 > 10 萬行 或 記憶體 < 128MB
→ 非緩衝查詢(mysqli_use_result / PDO::MYSQL_ATTR_USE_BUFFERED_QUERY=false)
預處理語句 + 需 num_rows
→ $stmt->store_result() 後再操作,用完 $stmt->free_result()
串流導出 CSV / JSON Lines
→ 非緩衝 + 迴圈 fetch_row 逐行輸出,記憶體恆定
LIMIT 就能解決大結果集記憶體問題 —— 錯,緩衝模式下 MySQL 仍會先傳輸全量結果再截取num_rows 直接報錯 —— 需先遍歷或 store_resultCommands out of sync