chunk内存不降反升是因eloquent查询缓存未关闭,模型实例持续驻留model::$instances中;chunkbyid适用于自增主键,否则易漏数据或死循环;真正降内存需避免模型实例化,改用db::table或pluck。

为什么 chunk 会内存不降反升?
不是 chunk 本身有问题,而是你没关掉 Eloquent 的查询缓存。Laravel 默认会把查出来的模型实例全保在 Model::$instances 静态属性里,哪怕分块查,上一批的模型对象仍被引用,GC 清不掉。
- 加
->withoutGlobalScopes()或->withTrashed()等链式调用后,更容易触发缓存累积 - 用
DB::table()替代Model::query()可绕过模型实例缓存,但失去访问器、强制类型转换等特性 - 最稳妥的是每块结束后手动清空:在闭包末尾加
Model::flushEventListeners(); Model::clearBootedModels();
chunk 和 chunkById 到底该选哪个?
看主键是不是自增整型且连续。如果是,chunkById 更稳;否则别硬上 —— 它依赖主键排序和范围扫描,遇到 UUID、复合主键、软删除字段干扰(比如 deleted_at IS NOT NULL 被跳过)时,会漏数据或死循环。
-
chunk基于OFFSET/LIMIT,大数据量下OFFSET越大越慢,但语义直观、兼容所有主键类型 -
chunkById实际执行类似WHERE id > ? ORDER BY id LIMIT ?,要求主键可比较、有索引、无重复值 - 如果表有频繁写入,两个方法都可能跳过刚插入/更新的行 —— 这是 MySQL 快照读的正常行为,不是 bug
怎么让 chunk 真正跑满 CPU 而不卡住?
默认单线程同步执行,10 万条数据分 1000 条/块,就是 100 次数据库往返 + PHP 循环,I/O 等待占大头。想提速,得拆开「查」和「处理」:
- 用
cursorPaginate()+ 队列(如dispatch(new ProcessChunk($cursor)))把每块扔进 Redis 队列异步跑 - 避免在闭包里做耗时操作(如 HTTP 请求、文件写入),先 collect 数据,再批量提交
- 确认 MySQL 的
wait_timeout和 Laravel 的 PDOoptions里没设过短,否则中间断连会抛PDOException: MySQL server has gone away
用 each 替代 chunk 会更省内存吗?
不会。二者底层都是迭代器 + LIMIT,each 只是语法糖,且隐藏了分页逻辑 —— 它内部仍调 chunk(1000),不能自定义大小,也不返回分页上下文。真正省内存的关键不在函数名,而在是否构建完整模型实例。
- 改用
cursorPaginate()+mapInto()或pluck()拿原始数组,比chunk少 30%~50% 内存占用 - 如果只需要统计或简单字段加工,直接
DB::table('users')->selectRaw('COUNT(*), SUM(points)')->get(),别碰模型层 - 注意:PHP 8.1+ 的
gc_mem_caches()在长循环里手动触发一次,有时比换方法还管用
分块不是银弹,它只解决“单次查询不爆内存”这一个问题。真正压垮服务的,往往是分块后没关日志、没限并发、或者在闭包里 new 了 10 个 Service 实例 —— 这些细节比选哪个函数重要得多。











