当前位置:首页 > 文章列表 > 文章 > php教程 > PHP Generators 怎样把数据库结果转成惰性序列

PHP Generators 怎样把数据库结果转成惰性序列

来源:17golang原创 2026-11-02 10:24:56 0浏览 收藏

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,完整结果数组已经存在,无法得到预期的应用层内存收益。生成器语法中暂停和恢复的是函数状态,并不会撤销先前已分配的内存。

已执行语句、逐行读取接口与消费者之间的静态依赖
图1:惰性行接口结构说明图。生成器关联逐行读取与当前行,消费者通过迭代使用它;连线不是查询执行时序。

让调用方管理非缓冲查询与释放

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 中限定范围,而不是读取开头几行后让剩余结果等待清理。

专用连接、结果游标、生成器引用与 finally 释放的资源归属
图2:游标资源归属说明图。调用方拥有专用连接和结果游标,并通过 finally 清理;生成器只是借用语句的读取接口。

把异常、提前结束与兼容性写进契约

查询执行异常发生在 execute()处;读取异常可能发生在迭代中;编码或下游写入也可能失败。因此异常处理应包围整个消费范围。示例让异常继续交给 CLI 入口处理,入口可记录诊断并设置失败退出码,不应对外输出连接凭据。

break只停止当前循环,不保证一个仍被引用的生成器立刻销毁。本文在调用方的 finally显式关闭游标,清理不依赖生成器析构。rowSequence的对象不得逃逸到这个作用域之外,游标关闭后不应继续恢复它。

调用方需要相应设计
一次顺序导出专用非缓冲连接加逐行消费
循环里查询关联信息改写查询或预先准备关联数据;必要时使用独立连接
可恢复的长任务另设计有界批次与进度记录,不把活游标当持久进度
随机访问、重复排序由数据库处理,或在明确内存预算内物化小集合

非缓冲读取只约束客户端取结果的方式,不保证数据库不排序、不使用临时空间,也不提供跨多个查询的一致快照。连接、事务、查询计划与业务一致性要求仍要独立设计。

返回 Generator 后内存就与行数完全无关了吗?

不能这样承诺。还要计算单行大小、驱动缓冲、下游累积、日志和输出缓冲。逐行接口避免的是“为了遍历而预先收集全部行”。

可以对这个结果直接 count 或遍历两次吗?

应按单次序列使用。需要总数时另写合适的计数查询,并考虑一致性;需要再次遍历时重新创建查询及序列,不把已消费对象当成数组。

iterator_to_array 会保留惰性吗?

不会。它把遍历结果重新收集为数组。小结果集可以明确选择物化,大导出则应保持逐行消费或只建立有界批次。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
PHP str_increment 怎样生成连续的字母数字编号PHP str_increment 怎样生成连续的字母数字编号
上一篇
PHP str_increment 怎样生成连续的字母数字编号
PHP preg_replace_callback_array 怎样按模式分发替换逻辑
下一篇
PHP preg_replace_callback_array 怎样按模式分发替换逻辑
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    842次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    842次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    921次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    897次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    725次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码