ThinkPHP 8.1.0 正式发布,深度优化路由与验证机制,完美兼容 PHP 8.4。本版本修复了数组路由配置异常,新增枚举值校验与高级数组验证功能,支持路由分类默认处理。作为高性能 PHP 框架的最新迭代,它延续了简洁实用的设计原则,提供更稳定的底层架构与更流畅的开发体验,助力开发者快速构建现代化 Web 应用与企业级系统。
tp6无真正游标支持,需用生成器封装游标分页:确保排序字段有索引且唯一(如id),再通过where('id', '>', $lastid)分页拉取数据,每次仅加载一页并及时释放内存。

在ThinkPHP 6.0中处理百万级数据导出或批量任务时,直接调用select()或chunk()极易触发“Allowed memory size exhausted”错误——这不是配置不够的问题,而是TP6底层未提供真正的游标(Cursor)支持,所有查询结果仍被完整加载进PHP内存所致。
为什么TP6没有真正的Cursor
ThinkPHP 6.x 的 Db 和 Model 层【不暴露 PDO 游标控制权】,所谓“Cursor遍历”实为误称。PDO 的 PDO::CURSOR_SCROLL 在 TP 中无法透出,MySQL 对其支持也极有限;即使声明了流式语义,fetch() 仍会将整结果集载入内存。官方文档里搜不到任何 cursor() 方法,所有自称支持 Cursor 的文章,实际都是手写 where('id', '>', $lastId) 模拟游标分页。
用生成器封装游标分页逻辑
这是TP6最稳、可调试、易落地的内存可控方案:把分页条件逻辑封装成生成器,每次只拉取一页数据,处理完立即释放内存。
第一步:确认排序字段有索引且唯一性足够——优先选自增 id,若用 created_at 必须加 id 复合去重,否则相同时间戳会导致漏数据或重复。
第二步:创建生成器函数,接收起始 $lastId 和每页数量 $size:
function cursorPaginate($lastId = 0, $size = 1000) {<br> while (true) {<br> $list = Db::name('user_log')<br> ->where('id', ' ->order('id DESC')<br> ->limit($size)<br> ->select();<br> if ($list->isEmpty()) break;<br> $lastId = $list->last()['id'];<br> yield $list;<br> }<br>}
第三步:用 foreach 遍历生成器,每页处理完后变量自动释放:
foreach (cursorPaginate(0, 500) as $page) {<br> foreach ($page as $row) {<br> // 处理单条记录,如写入文件、发送消息<br> }<br> // 此处 $page 自动销毁,内存立即回收<br>}
禁用 chunk() 的三个理由
方法一:chunk() 底层仍是多次 SELECT 全量加载,每批结果都完整驻留内存,不适合导出类场景。
方法二:chunk() 默认启用事务,大批量操作易锁表、拖慢数据库响应,且无法跳过已处理数据。
方法三:chunk() 不支持按非主键字段游标推进,一旦排序字段存在重复值,就会漏数据——而生成器+where分页可手动控制边界条件。
紧急情况下的 memory_limit 临时提升
方法1(CLI环境推荐):在PowerShell中执行,【双引号不可省】,否则 -1 会被当作命令参数截断:
php -d "memory_limit=-1" artisan export:users
方法2(Web环境可用):仅对当前脚本生效,需放在逻辑最开头,且不能高于 php.ini 硬上限:
ini_set('memory_limit', '512M');
注意:这只是救火手段,不能替代游标分页;若脚本已分配超400M内存,ini_set() 无法回收已占用部分。










