2026年PHP如何高效地从数据库读取百万级记录_使用yield生成器逐行处理

PHP如何高效地从数据库读取百万级记录_使用yield生成器逐行处理blockquote 不能用 fetch all 或循环 fetch 全部存数组 因为会一次性将全部结果集加载进内存 100 万行 每行 2KB 2GB 直接触发内存耗尽错误 即使 while fetch 若未禁用缓冲 如 PDO 默认 buffered 仍会全量缓存 必须配合游标分页 有序索引与 unbuffered 查询 通过 yield blockquote

大家好,我是讯享网,很高兴认识大家。这里提供最前沿的Ai技术和互联网信息。



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

php如何高效地从数据库读取百万级记录_使用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 页”,那就老老实实加缓存或预计算总数+偏移优化,别硬扛。

小讯
上一篇 2026-04-30 07:56
下一篇 2026-04-30 07:54

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/282532.html