PHP Generators 怎样把数据库结果转成惰性序列
PHP Generators 可以在每次读取数据库一行后 yield,让调用方通过 foreach按需消费,不必先调用 fetchAll()。但生成器的惰性与数据库驱动的结果缓冲是两层机制:要降低大结果集的客户端缓冲占用,还需选择适合的查询模式,并管理游标释放。
官方地址:https://www.php.net/
先约定这是一次性的行序列
假设 CLI 导出任务需要按商品 ID 顺序读取记录并逐行输出。接口应承诺“依次提供行”,而不是“返回可随意索引、计数和重放的集合”。生成器概述说明,它能为循环逐个提供值,无须预先构建完整数组,也不提供随机访问。
| 接口约定 | 本文的选择 |
|---|---|
| 输入 | 已经执行的 PDOStatement |
| 输出 | 每次一行关联数组 |
| 查询何时执行 | 由调用方显式 execute |
| 游标由谁释放 | 创建查询的调用方 |
| 是否可重复遍历 | 按单次消费设计,需要重读则重新查询 |
这个划分刻意让 SQL 执行与序列读取分开。生成器函数被调用时返回对象,函数体在迭代时推进;由于本文将 execute()放在外面,SQL 本身不会等到第一次 foreach才执行。这样调用方能清楚地知道何时开始占用连接。
用 fetch 和 yield 实现读取接口
保存下面函数,随后接上连接与消费代码。它只负责将行读取适配为序列,不接管语句的执行、重试或释放。
fetch(PDO::FETCH_ASSOC)) !== false) {
yield $row; // 当前行交给调用方,下一次迭代才继续读取。
}
}
fetch 文档定义了关联数组读取模式及 false返回条件。配合异常错误模式,数据库读取故障会作为异常传播,而不是被业务代码当成一个普通行处理。
不要在函数内先 fetchAll(),再遍历那个数组并 yield:虽然接口形式仍是 Generator,完整结果数组已经存在,无法得到预期的应用层内存收益。生成器语法中暂停和恢复的是函数状态,并不会撤销先前已分配的内存。

让调用方管理非缓冲查询与释放
MySQL 缓冲说明指出,默认缓冲模式会将结果传到客户端保存;非缓冲模式减少这一层内存占用,但结果未取完时同一连接不能继续发送其他查询。因此适合顺序处理大量行的专用读取连接,不能直接替换所有业务连接的配置。
下面假定已有 products表,含 id与 sku字段,id是可排序且在 PHP 整数范围内的正整数。环境变量保存 MySQL DSN、用户与密码,DSN 应指定正确字符集。本文没有执行数据库或输出真实商品数据。
// 与 rowSequence 放在同一 PHP CLI 文件中。
$dsn = getenv('DB_DSN');
$user = getenv('DB_USER');
$password = getenv('DB_PASSWORD');
if ($dsn === false || $user === false || $password === false
|| !str_starts_with($dsn, 'mysql:')) {
throw new RuntimeException('缺少有效的 MySQL 连接配置');
}
// 这是专门用于顺序读取的连接;不要在消费循环里用它做其他查询。
$pdo = new PDO($dsn, $user, $password, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false,
]);
$statement = $pdo->prepare(
'SELECT id, sku FROM products WHERE id > :after_id ORDER BY id'
);
$rows = null;
try {
$statement->bindValue(':after_id', 0, PDO::PARAM_INT);
$statement->execute();
$rows = rowSequence($statement);
foreach ($rows as $row) {
// 逐行编码,不把记录追加到一个不断增长的数组。
echo json_encode(
$row,
JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR
), PHP_EOL;
// 业务需要时可 break;外层 finally 仍负责释放游标。
}
} finally {
// rowSequence 不拥有游标:释放义务明确属于这一层。
unset($rows);
$statement->closeCursor();
}
这里没有将列值强制转整数或浮点数,避免把商品编码、超大编号等业务值误改。消费逻辑也可以替换成写文件或批量发送,但应限制下游队列长度;如果输出缓冲一直累积内容,整条链路仍可能占用大量内存。
closeCursor 文档说明它释放连接以便执行其他 SQL。调用可能需要处理剩余结果,不能将其理解成“立刻取消服务器端工作”或承诺零耗时。若只需要有限行,应尽可能在 SQL 中限定范围,而不是读取开头几行后让剩余结果等待清理。

把异常、提前结束与兼容性写进契约
查询执行异常发生在 execute()处;读取异常可能发生在迭代中;编码或下游写入也可能失败。因此异常处理应包围整个消费范围。示例让异常继续交给 CLI 入口处理,入口可记录诊断并设置失败退出码,不应对外输出连接凭据。
break只停止当前循环,不保证一个仍被引用的生成器立刻销毁。本文在调用方的 finally显式关闭游标,清理不依赖生成器析构。rowSequence的对象不得逃逸到这个作用域之外,游标关闭后不应继续恢复它。
| 调用方需要 | 相应设计 |
|---|---|
| 一次顺序导出 | 专用非缓冲连接加逐行消费 |
| 循环里查询关联信息 | 改写查询或预先准备关联数据;必要时使用独立连接 |
| 可恢复的长任务 | 另设计有界批次与进度记录,不把活游标当持久进度 |
| 随机访问、重复排序 | 由数据库处理,或在明确内存预算内物化小集合 |
非缓冲读取只约束客户端取结果的方式,不保证数据库不排序、不使用临时空间,也不提供跨多个查询的一致快照。连接、事务、查询计划与业务一致性要求仍要独立设计。
返回 Generator 后内存就与行数完全无关了吗?
不能这样承诺。还要计算单行大小、驱动缓冲、下游累积、日志和输出缓冲。逐行接口避免的是“为了遍历而预先收集全部行”。
可以对这个结果直接 count 或遍历两次吗?
应按单次序列使用。需要总数时另写合适的计数查询,并考虑一致性;需要再次遍历时重新创建查询及序列,不把已消费对象当成数组。
iterator_to_array 会保留惰性吗?
不会。它把遍历结果重新收集为数组。小结果集可以明确选择物化,大导出则应保持逐行消费或只建立有界批次。
PHP str_increment 怎样生成连续的字母数字编号
- 上一篇
- PHP str_increment 怎样生成连续的字母数字编号
- 下一篇
- PHP preg_replace_callback_array 怎样按模式分发替换逻辑
-
- 文章 · php教程 | 1小时前 |
- PHP readonly 类怎样避免配置对象被意外修改
- 200浏览 收藏
-
- 文章 · php教程 | 1小时前 | 并发 · HTTP · PHP · 工程实践 · php 并发请求 curl_multi_exec curl_multi curl_multi_info_read
- PHP curl_multi 怎样并发请求并读取完成队列
- 188浏览 收藏
-
- 文章 · php教程 | 1小时前 | 依赖管理 · PHP · php PHP扩展 Composer PHP版本 composer.lock config.platform check-platform-reqs
- PHP Composer 平台配置怎样锁定运行时版本
- 185浏览 收藏
-
- 文章 · php教程 | 1小时前 |
- PHP str_increment 怎样生成连续的字母数字编号
- 356浏览 收藏
-
- 文章 · php教程 | 1小时前 | PHP · php iconv stream_filter_append stream_copy_to_stream
- PHP stream_filter_append 怎样增量转换大文件
- 398浏览 收藏
-
- 文章 · php教程 | 1小时前 |
- PHP Fiber 怎样把协作式任务接入事件循环
- 135浏览 收藏
-
- 文章 · php教程 | 1小时前 |
- PHP 枚举怎样给数据库值提供安全映射
- 424浏览 收藏
-
- 文章 · php教程 | 1小时前 | php教程 · 元数据 属性 Attributes PHP 8 ReflectionClass ReflectionMethod ReflectionAttribute
- PHP 8 属性怎样用反射读取自定义元数据
- 395浏览 收藏
-
- 文章 · php教程 | 1小时前 |
- PHP PDO 怎样设置 ATTR_EMULATE_PREPARES 避免类型误判
- 203浏览 收藏
-
- 文章 · php教程 | 1天前 |
- PHP Fiber 中异常抛出与 finally 清理顺序
- 441浏览 收藏
-
- 文章 · php教程 | 1天前 | 性能 · PHP · php 大文件传输 stream_set_chunk_size
- PHP stream_set_chunk_size 对大文件传输的缓冲影响
- 288浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 842次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 842次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 921次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 897次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 725次使用
-
- PHP fgetcsv escape 参数显式设置与 RFC 4180 兼容处理
- 2026-10-26 501浏览
-
- PHP JSON_THROW_ON_ERROR 抛错后怎么保留原始字段位置
- 2026-09-09 501浏览
-
- PHP 8.5 array_last() 怎么处理空数组:从 null 结果到兼容旧版本的 Polyfill
- 2026-08-16 501浏览
-
- 宝塔配置Ruby环境:RVM+Nginx反代教程
- 2026-05-29 501浏览
-
- unset函数作用范围详解
- 2026-05-29 501浏览

