thinkphp 6.0 不支持 db::cursor(),需用 where + order + limit 模拟游标分页导出千万级数据,避免内存溢出和超时;应选 id 作游标字段、禁用超时、刷输出缓冲、确保索引优化。

Db::cursor() 在 ThinkPHP 6.0 中根本不存在——别被标题误导,TP6 没有原生游标查询能力。
你真正需要的,是「用 where + order + limit 模拟游标分页」来导出千万级数据,同时避免内存溢出和 MySQL 连接超时。
为什么 Db::cursor() 会报错或根本找不到
ThinkPHP 6.x 的 Db 和 Model 层未暴露 PDO 游标控制权,也未实现 cursor() 方法。所有声称“TP6 支持 cursor 查询”的代码,实际都是手动拼 where('id', '>', $lastId)。官方文档里搜不到 cursor,源码里也查不到该方法定义。
千万级导出必须用「游标分页」而非 chunk()
chunk() 看似方便,但它本质仍是分批 select:每一批都完整加载进 PHP 内存,且依赖 offset,越往后越慢;导出 500 万行时,chunk(1000) 会执行 5000 次 SQL,中间任意一次失败就得重头来。
- ✅ 正确做法:用排序字段(如
id)做游标锚点,每次只查「比上一页最后 id 小」的新数据 - ❌ 错误做法:用
paginate()或chunk()做全量导出——count()本身就会卡死,且内存随批次线性增长 - ⚠️ 注意:
created_at不能单独当游标字段——时间戳重复会导致漏行或重复导出;优先选id,或组合id+created_at
导出循环中必须带 set_time_limit(0) 和显式 ob_flush()
默认 PHP 脚本超时是 30 秒,导出千万行至少要跑几分钟甚至几十分钟;不关超时,脚本中途就中断。同时,输出缓冲区不主动刷,浏览器会一直等,直到全部写完才响应。
-
set_time_limit(0)放在循环开始前,禁用超时限制 - 每处理完一批(比如 1000 行),调用
ob_flush()和flush()强制输出到客户端 - 避免用
echo直接拼 CSV 字符串——改用fputcsv($fp, $row)写临时文件,再readfile()输出,更稳
MySQL 索引和事务配置决定成败
游标分页性能完全依赖索引。如果 ORDER BY id DESC + WHERE id 没走索引,就会全表扫描,导出从「慢」变成「卡死」。
- 确保排序字段(如
id)有主键或二级索引;复合游标如(status, id)需联合索引 - 关闭事务自动提交:
Db::startTrans()不要套整个导出循环——它会让 MySQL 锁住 MVCC 快照,内存占用飙升 - 导出过程全程保持
DB::connect()->execute('SET SESSION autocommit = 1'),避免长事务
where + order 看似能跑通,但数据量一上去就退化成全表扫描——此时你不是在导出,是在给 MySQL 做压力测试。










