不能用 fetch_all() 或循环 fetch() 全部存数组,因为会一次性将全部结果集加载进内存,100 万行 × 每行 2KB ≈ 2GB,直接触发内存耗尽错误;即使 while fetch(),若未禁用缓冲(如 PDO 默认 buffered),仍会全量缓存,必须配合游标分页、有序索引与 unbuffered 查询,通过 yield 实现边取边处理的真正流式读取。

直接结论:用 yield 逐行处理百万级数据库记录,内存可压到几 MB,但必须配合游标分页 + 有序索引,不能用 LIMIT OFFSET。
PHP 默认 PDO/MySQLi 的 fetch_all() 会把全部结果集一次性加载进内存。100 万行 × 每行平均 2KB ≈ 2GB 内存 —— 这不是“慢”,是直接 Fatal error: Allowed memory size of ... exhausted。
即使改用 while ($row = $stmt->fetch()),若底层驱动未启用 PDO::MYSQL_ATTR_USE_BUFFERED_QUERY = false(MySQLi 默认 unbuffered),依然会缓冲全部结果。
真正安全的做法是:不缓存、不攒数组、不依赖 PHP 层分页,让数据库每次只吐出你需要的那一批。
立即学习“PHP免费学习笔记(深入)”;
核心不是“PHP 怎么循环”,而是“怎么让数据库持续推送、PHP 边取边交还控制权”。以下为最小可行结构:
function readLargeTable(PDO \(pdo, string \)cursor = null): Generator$sql .= " ORDER BY created_at DESC, id DESC LIMIT 2000"; $stmt = $pdo->prepare($sql); $stmt->execute($params); while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { yield $row; }}
- 必须确保
ORDER BY created_at DESC, id DESC 和WHERE (created_at, id) < (?, ?)字段顺序、方向完全一致,否则索引失效 -
LIMIT值建议 1000–5000,太小增加查询次数,太大仍可能内存波动 - 游标值(如
\(cursor['time'])必须来自上一页末条记录,不能前端传页码或字符串拼接 - 字段类型要严格校验:
\)cursor[‘time’]必须是合法Y-m-d H:i:s,$cursor[‘id’]必须是 int,否则 SQL 注入或类型隐式转换导致漏数据
当 OFFSET 超过 10 万,MySQL 就得扫描并丢弃前 100 万行 —— 即使有索引,也是 type: index 全索引扫描,I/O 和 CPU 都爆炸。实测百万级表 LIMIT , 2000 可能耗时 8 秒以上;而游标分页稳定在 20–50ms。
关键前提:
-
created_at 字段必须有复合索引:INDEX idx_created_id (created_at DESC, id DESC)
- 不能用
datetime 字段单独做游标,因为存在时间相同的情况,必须加入 id 消除歧义
- 如果业务允许,优先用自增
id 做游标(WHERE id < ? ORDER BY id DESC),最简单可靠
很多人写了 yield 却没生效,是因为没关掉 MySQL 的结果集缓冲:
- PDO 必须显式设置:
\(pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
- MySQLi 必须用
mysqli_stmt::get_result() 之前调用 mysqli_stmt::execute(),且连接需开启 MYSQLI_CLIENT_FOUND_ROWS
- PostgreSQL 更干净,默认就是 unbuffered,但要注意
PDO::ATTR_EMULATE_PREPARES = false,否则游标参数会被转成字符串丢失精度
- 生成器函数返回后,
\)stmt 和 $pdo 仍持有连接,记得在长期运行脚本中控制连接生命周期,避免 Too many connections
游标分页 + yield 不是银弹:它要求排序字段绝对有序、不可空、有索引,且无法跳转任意页 —— 如果你真需要“跳到第 827 页”,那就老老实实加缓存或预计算总数+偏移优化,别硬扛。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/282532.html