最稳妥的遍历方式是mysqli_fetch_assoc(),适合不确定结果集大小的场景,内存恒定;PDO::fetchAll()适合小结果集快速开发但易OOM;大导出必须用mysqli_unbuffered_query()。

mysqli_fetch_assoc() 逐行取数据最常用
这是生产环境里最稳妥的遍历方式,尤其适合不确定结果集大小的场景。它每次只从结果集中取出一行,转成关联数组,内存占用恒定,不会因查出10万条记录而爆内存。
常见错误是误用 mysqli_num_rows() 判断空结果:如果用的是 mysqli_unbuffered_query(),调用 mysqli_num_rows() 会返回 -1 或直接失败——因为非缓冲查询不预加载全部结果,行数根本不可知。
- 必须配合
while ($row = mysqli_fetch_assoc($result))使用,不能先count($result)或foreach ($result as ...)——$result是资源句柄,不是数组 - 若需重用结果集(比如先计数再遍历),得用
mysqli_data_seek($result, 0)回到开头;但非缓冲查询不支持该操作 - 字段名区分大小写,
$row['ID']和$row['id']可能是两个键,取决于 MySQL 的列定义和 SQL 模式
PDO::fetchAll() 适合小结果集快速开发
一次性把所有行读进 PHP 数组,代码简洁,适合后台管理页查几十条用户、配置项这类轻量场景。但它的代价是内存直线上升——10万条记录 × 每条2KB ≈ 200MB 内存,PHP 进程可能被 OOM Killer 干掉。
容易踩的坑是类型混淆:PDO::FETCH_ASSOC 返回关联数组,PDO::FETCH_NUM 返回数字索引,混用会导致 Undefined index 错误;更隐蔽的是浮点字段(如 DECIMAL(10,2))默认以字符串返回,直接参与计算会触发隐式转换,精度丢失。
立即学习“PHP免费学习笔记(深入)”;
- 显式指定 fetch mode:
$stmt->fetchAll(PDO::FETCH_ASSOC),别依赖默认值 - 大结果集禁用:哪怕只是临时调试,也建议加
LIMIT 100防止意外拖垮服务 - 日期字段(
DATETIME)返回的是字符串,要转DateTime对象得手动 new,PDO 不自动处理
foreach 遍历前必须先 fetchAll 或 fetchAll + yield
foreach 不能直接作用于 $result 或 $stmt,因为它们不是数组。网上很多错误示例写成 foreach ($result as $row),实际会报 Fatal error: Cannot iterate over string 或静默失败。
真正可行的路径只有两条:一是先 $rows = $stmt->fetchAll(),再 foreach ($rows as $row);二是用生成器封装流式读取,兼顾可读性与内存安全:
function streamQuery(PDO $pdo, string $sql): Generator {
$stmt = $pdo->query($sql);
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
yield $row;
}
}
// 使用
foreach (streamQuery($pdo, 'SELECT * FROM logs') as $log) {
process($log);
}- 别信“
foreach更快”的直觉——它只是语法糖,底层仍靠fetch调用,性能差异可忽略 - 嵌套
foreach时,内层别复用外层变量名,比如都叫$row,PHP 8+ 会覆盖导致逻辑错乱 - 生成器方式无法回溯,也不能用
count()或array_slice(),需提前规划好消费逻辑
mysqli_unbuffered_query() 是大数据导出的唯一可靠选择
当你要导出几十万行 CSV、生成报表或同步到外部系统时,必须用 mysqli_unbuffered_query()。它让 MySQL 边查边发,PHP 边收边处理,内存始终维持在 KB 级别。
代价也很明确:不能获取总行数、不能倒序遍历、不能重复读取、不能用 mysqli_data_seek()。一旦执行过 mysqli_fetch_*,结果集就彻底消耗完毕,再次调用会返回 false。
- 连接必须设为
MYSQLI_CLIENT_FOUND_ROWS模式,否则某些语句(如带 LIMIT 的 UPDATE)可能行为异常 - 务必在循环结束后调用
mysqli_free_result($result),否则连接会一直占用结果集资源 - 不能和事务混用——未提交的事务下,非缓冲查询可能阻塞或返回不一致数据
实际项目里,90% 的遍历需求落在 mysqli_fetch_assoc() 和 PDO::fetchAll() 之间。选哪个不取决于“哪个高级”,而取决于你心里有没有那根内存警戒线:查列表页?用 fetchAll;导日志?上 unbuffered_query;做后台任务?宁可多写两行 while,也别赌 memory_limit。



















