Lazy Collection 通过生成器按需获取数据,每次只取一批(默认1000行)并即时释放引用,避免全量加载;cursor()返回原始数组,lazy()创建完整模型实例。

Lazy Collection 是怎么避免一次性加载全部数据的
Lazy Collection 不是把整个数据集读进内存,而是用生成器(Generator)按需产出每一项。它不会像普通 Collection 那样调用 get() 后立刻执行查询并实例化所有模型;相反,它把查询构建好,等到你真正遍历(比如用 each()、filter() 或 toArray())时才逐步 fetch —— 每次只取一批(默认 1000 行),且每行处理完就释放引用。
关键点在于:它底层包装的是 PDOStatement::fetch() 的迭代过程,不是 array_map 那种全量数组操作。
什么时候该用 cursor() 而不是 lazy()
cursor() 和 lazy() 都返回 LazyCollection,但行为有本质区别:
-
cursor()绕过 Eloquent 模型实例化,直接返回原始数组(stdClass或关联数组),省掉模型构造、属性赋值、访问器调用等开销,内存更低、速度更快 -
lazy()仍会为每一行创建完整 Eloquent 模型,适合需要调用$model->getAttribute()、$model->relation或修改后保存的场景 - 如果只是导出、统计、清洗字段,优先选
cursor();如果要复用模型逻辑(如$user->fullName访问器),再考虑lazy()
示例对比:
App\Models\User::cursor()->each(function ($row) {
// $row 是 array 或 stdClass,无访问器、无事件、无类型转换
});
App\Models\User::lazy()->each(function ($user) {
// $user 是完整 User 模型实例,可调用 $user->name, $user->posts, $user->save()
});
链式操作中哪些方法会触发全量加载
LazyCollection 表面支持大部分 Collection 方法,但部分操作必须“看到全部数据”才能完成,会提前耗尽生成器、失去懒加载优势:
-
count()、sum()、avg()、max()、min():必须遍历全部,但不会把所有模型留在内存 —— 只存中间结果(如累加值),这点比普通 Collection 好 -
toArray()、all()、values():彻底放弃懒加载,把所有项转成数组,内存暴涨 -
sortBy()、groupBy():需要随机访问或分组聚合,会先收集全部数据再处理,等价于toArray()+ 普通 Collection 操作 - 安全的操作包括:
filter()、map()、skip()、take()、chunk()—— 它们保持流式处理特性
错误示范(看似懒,实则全载):
App\Models\LargeLog::lazy()
->sortBy('created_at') // ⚠️ 这里已把全部记录加载进内存
->take(10)
->each(...);
配合 chunkById() 和游标分页进一步控内存
LazyCollection 本身不解决单次查询太慢或 MySQL 连接超时问题。大数据集下,建议组合使用:
- 对超大表(千万级),别只靠
lazy(),改用chunkById()手动分页,每次查WHERE id BETWEEN ? AND ?,避免OFFSET性能衰减 - 若需前端分页,用游标(
cursorPaginate())代替paginate(),避免COUNT(*)全表扫描 - 导出场景中,用
streamDownload()+cursor()直接写入响应流,完全不缓存结果
例如流式 CSV 导出:
return response()->streamDownload(function () {
$handle = fopen('php://output', 'w');
fputcsv($handle, ['id', 'email', 'created_at']);
App\Models\User::cursor()->each(function ($user) use ($handle) {
fputcsv($handle, [$user->id, $user->email, $user->created_at]);
});
fclose($handle);
}, 'users.csv');
注意:cursor() 返回的字段名默认是数据库列名(如 created_at),不是模型访问器定义的键(如 createdAt),这点容易忽略。










