
本文揭示了一个因错误使用 while 循环读取单行数据库结果而引发的严重内存泄漏问题,重点分析 mysqli_fetch_assoc() 被误置于恒真循环中所导致的无限执行与内存爆炸现象,并提供安全、高效的修复方案。
本文揭示了一个因错误使用 while 循环读取单行数据库结果而引发的严重内存泄漏问题,重点分析 mysqli_fetch_assoc() 被误置于恒真循环中所导致的无限执行与内存爆炸现象,并提供安全、高效的修复方案。
在 PHP Web 开发中,数据库查询后对结果集的遍历必须严格匹配预期数据量。本案例中的 funGetToken() 函数表面看仅执行两条简单 SELECT 查询,却触发了高达 115 GB 的内存分配(allocated 115540492288 bytes),最终因内存耗尽崩溃。根本原因并非查询本身复杂,而在于一段隐蔽的逻辑死循环:
// ❌ 危险代码:$row 未被更新,导致无限循环!
$row = mysqli_fetch_assoc($result);
if (!empty($row)) {
while ($row) { // ← $row 始终为真(非空数组),永不退出!
// ... 大量 json_encode() 和 array_push() 操作
array_push($arrayMain, json_encode($viArrayValues));
}
}此处 while($row) 构成了典型的“伪循环”——$row 是一个从 mysqli_fetch_assoc() 获取的静态数组变量,其值在循环体内从未被重新赋值。只要 $row 非空(即查询返回至少一行),该 while 就会无限执行,持续向 $arrayMain 中追加完全相同的 JSON 字符串。每一次 json_encode() 都会复制一份数据,叠加 array_push() 的数组扩容开销,内存呈线性甚至指数级增长,直至耗尽所有可用内存。
✅ 正确做法:对单行结果应直接处理,无需 while;对多行结果才用 while 迭代。
修复后的核心逻辑如下(已精简冗余分支):
立即学习“PHP免费学习笔记(深入)”;
// ✅ 正确:单行查询直接处理,无循环
$sqlCmd = "SELECT ... FROM tb_tokens INNER JOIN tb_gallery ... WHERE tb_tokens.id = ?";
// 推荐使用预处理语句防止 SQL 注入(关键安全改进)
$stmt = $connection->prepare($sqlCmd);
$stmt->bind_param("i", $id);
$stmt->execute();
$result = $stmt->get_result();
if ($row = $result->fetch_assoc()) {
// 构建单条记录数组
$viArrayValues = [
'id' => $id,
'symbol' => $row['symbol'],
'name' => $row['name'],
'idtype' => $row['idtype'],
'decimalCases' => $row['decimalCases'],
'maxSupply' => $row['maxSupply'],
'stake' => $row['stake'],
'delegation' => $row['delegation'],
'active' => $row['active'],
'description' => $row['description'],
'path' => $row['path'] ?? '',
'idTbGallery' => $row['idTbGallery'] ?? ''
];
$arrayMain[] = json_encode($viArrayValues); // 更简洁的数组追加写法
} else {
// 回退查询:无图片关联时降级获取基础字段
$fallbackSql = "SELECT symbol, name, idtype, decimalCases, maxSupply, stake, delegation, description, active FROM tb_tokens WHERE id = ?";
$stmt = $connection->prepare($fallbackSql);
$stmt->bind_param("i", $id);
$stmt->execute();
$result = $stmt->get_result();
if ($row = $result->fetch_assoc()) {
$viArrayValues = [
'id' => $id,
'symbol' => $row['symbol'],
'name' => $row['name'],
'idtype' => $row['idtype'],
'decimalCases' => $row['decimalCases'],
'maxSupply' => $row['maxSupply'],
'stake' => $row['stake'],
'delegation' => $row['delegation'],
'active' => $row['active'],
'description' => $row['description'],
'path' => '',
'idTbGallery' => ''
];
$arrayMain[] = json_encode($viArrayValues);
}
}关键优化与注意事项:
- 杜绝手动拼接 SQL:原始代码中 WHERE tb_tokens.id=".$id 存在严重 SQL 注入风险,必须改用 mysqli::prepare() + bind_param();
- 释放资源及时:$result->free() 或使用预处理语句自动管理内存;
- 避免重复 json_encode:若后续需多次使用结构化数据,建议先构建原生数组,最后统一 json_encode($arrayMain) 输出;
- 类型安全校验:对 $id 做 (int)$id 强制转换或 filter_var($id, FILTER_VALIDATE_INT) 验证,防止非法输入;
- 内存监控辅助诊断:开发阶段可加入 echo memory_get_usage(true) 日志,定位内存峰值位置。
综上,该问题本质是控制流逻辑缺陷而非数据量过大。开发者需始终明确:mysqli_fetch_assoc() 返回的是单次结果,循环遍历仅适用于 n > 1 的结果集;单行场景下,一次判断 + 直接处理才是高效且安全的实践。











