ThinkPHP6.0处理百万级数据时直接调用select或chunk易导致内存溢出,因其缺乏真正游标支持。可通过生成器封装游标分页,利用带索引的唯一字段(如id)以where('id','',$lastId)逐页拉取数据,每页处理完即释放内存。
在ThinkPHP 6.0中处理百万级数据导出或批量任务时,直接调用select()或chunk()极易触发“Allowed memory size exhausted”错误——这并非单纯的配置问题,而是TP6底层缺乏真正的游标支持,所有查询结果仍被完整加载进PHP内存所致。
TP6无真正游标支持,需用生成器封装游标分页:确保排序字段有索引且唯一(如id),再通过where('id', '>', $lastId)分页拉取数据,每次仅加载一页并及时释放内存。

长期稳定更新的攒劲资源: >>>点此立即查看<<<
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) {
while (true) {
$list = Db::name('user_log')
->where('id', '<', $lastId ?: '9999999999')
->order('id DESC')
->limit($size)
->select();
if ($list->isEmpty()) break;
$lastId = $list->last()['id'];
yield $list;
}
}
第三步,用foreach遍历生成器,每页处理完后变量自动释放:
foreach (cursorPaginate(0, 500) as $page) {
foreach ($page as $row) {
// 处理单条记录,如写入文件、发送消息
}
// 此处 $page 自动销毁,内存立即回收
}
第一,chunk()底层仍然是多次SELECT全量加载,每批结果都完整驻留内存,完全不适合导出类场景。
第二,chunk()默认启用事务,大批量操作极易锁表、拖慢数据库响应,且无法跳过已处理过的数据。
第三,chunk()不支持按非主键字段进行游标推进。一旦排序字段存在重复值,必然导致数据遗漏——而生成器+where分页可以手动控制边界条件,从根本上避免此问题。
方法1(CLI环境推荐):在PowerShell中执行时,【双引号不可省】,否则-1会被当作命令参数截断:
php -d "memory_limit=-1" artisan export:users
方法2(Web环境可用):仅对当前脚本生效,需放在逻辑最开头,且不能高于php.ini硬上限:
ini_set('memory_limit', '512M');
值得注意的是,这仅是救火手段,绝不能替代游标分页方案。如果脚本已经分配了超过400M的内存,ini_set()无法回收已占用的部分。
侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述