foreach遍历get()必爆内存;chunk()与chunkbyid()选错致数据丢失或重复;cursor()省对象内存但非流式;批量更新禁用循环update,应批量sql;分块操作依赖索引。

直接用 foreach 遍历 get() 结果必然爆内存,这不是配置问题,是 Eloquent 模型实例化本身的开销叠加导致的。百万级数据下,哪怕每行只占 2KB,全加载就是 2GB —— PHP 默认内存限制根本扛不住。
chunk() 和 chunkById() 怎么选
两者都分批查,但底层机制不同,选错会丢数据或重复处理:
-
chunk()基于OFFSET+LIMIT,依赖ORDER BY id(默认),但如果表里有id被删又重插、或并发写入导致主键不连续,后续批次可能跳过或重复某些记录 -
chunkById()每次查询都带WHERE id > ?条件,靠主键单调递增保证顺序和唯一性,更适合更新类操作 - 如果表主键不是自增整数(比如 UUID 或字符串),
chunkById()会报错或行为不可控,此时只能退回到chunk(),但必须加orderBy('id')显式指定排序字段 - 两者都不支持
with()关联预加载 —— 一旦用了,Eloquent 会为每条记录触发额外查询,批量优势归零
cursor() 真的更省内存吗
别轻信“流式读取”宣传。MySQL 默认使用客户端游标,cursor() 实际仍是把整结果集一次性拉到 PHP 进程内存里,只是用生成器逐行 yield,避免模型实例化开销。它省的是对象创建内存,不是网络传输或数据库缓冲区内存:
- 适合纯读场景:导出 CSV、生成报表、发通知(只读且字段精简)
- 绝对不能在
foreach循环里调$user->update()—— 游标状态会被破坏,后续迭代失效,甚至 MySQL 报错MySQL server has gone away - 如果数据量超 100 万行,且 PHP 内存限制设得紧(比如 128MB),
cursor()可能比chunkById(500)更容易触顶,因为结果集没释放 - 验证方式:用
memory_get_usage(true)打点对比,别凭感觉
批量更新千万别循环调 update()
写个 foreach 包着 Model::where(...)->update() 是性能杀手,每条 update 都是一次独立 SQL 请求 + 连接开销:
- 正确做法是收集 ID 列表,用原生 SQL 一次更新:
DB::table('orders')->whereIn('id', $ids)->update(['status' => 'done']) - 如果每个 ID 要更新不同字段值(比如不同用户积分不同),就不能用
whereIn,得用INSERT ... ON DUPLICATE KEY UPDATE模式,Laravel 本身不提供封装,需手写DB::statement() - 事务只包单个分表操作 —— 跨
orders_2023和orders_2024的事务是假原子性,InnoDB 不认逻辑同名表 - 批量插入同理:用
DB::table()->insert($rows),别用Model::create()循环
最易被忽略的点:所有分块方法都依赖索引。如果 chunkById() 的字段没索引,每次查询都要全表扫描;cursor() 的 ORDER BY 字段没索引,排序成本爆炸。别只调 API,先看 EXPLAIN。











