处理百万级日志需避免get()导致内存溢出,应使用chunk分批(TP6用chunk()、Laravel用chunk()/cursor()/chunkById()),并关闭模型缓存、改用原生查询或聚合SQL,必要时启用生成器惰性加载。

处理百万级用户日志时,直接调用get()会瞬间耗尽512MB内存,进程被系统kill——这不是配置不够,而是加载方式错了。
ThinkPHP中用chunk分批拉取数据
ThinkPHP 6.x 的 chunk() 是唯一开箱即用的分批方案,它不依赖游标,而是用主键范围+limit模拟流式读取,适合清洗、导出、推送等后台任务。
第一步:传入闭包回调,不能链式调用 → 否则 chunk 不执行
第二步:每批数量设为 300~500,单条记录越大,批次越小;设成 1000 容易触发 PHP 内存临界点
立即学习“PHP免费学习笔记(深入)”;
第三步:在闭包内处理完立即 unset 变量,尤其避免把整批结果赋给新数组
示例代码:
Db::name('user_log')→where('status', 1)→chunk(400, function ($logs) {
foreach ($logs as $log) {
// 处理单条日志
processLog($log);
}
// 这一步必须做,否则上一批对象残留引用
unset($logs);
});
Laravel中chunk与cursor的本质区别
chunk 是分页式批量拉取,cursor 是数据库连接保持下的逐行迭代——二者内存曲线完全不同。
方法一:用 chunk() 做可中断的写操作
适合需要更新、发通知、写文件等场景;支持返回 false 中断后续批次;但每批仍构建完整模型实例,内存不会归零。
方法二:用 cursor() 做只读遍历
【必须关闭模型缓存】,否则 Model::$instances 持续累积;底层使用 PDO unbuffered query,内存占用稳定在 2~3MB;无法中途修改数据,也不能跳过某条记录。
方法三:用 chunkById() 替代普通 chunk
仅适用于自增主键且无删除空洞的表;执行 WHERE id > ? ORDER BY id LIMIT ?,比 offset 分页快 8 倍;遇到 UUID 或软删除字段时会漏数据,【切勿在 deleted_at IS NULL 的表上硬套】。
真正降内存的关键动作
不是换函数名,而是切断模型实例驻留路径。
关闭 Eloquent 查询缓存:
在 chunk 闭包开头加 Model::flushEventListeners(); Model::clearBootedModels();
改用原生查询绕过模型层:
DB::table('users')->select('id', 'name', 'email')->chunk(500, function ($rows) { ... }) —— 这样每批只占原始内存的 1/3。
如果只需统计或拼接字符串,直接写聚合 SQL:
DB::table('orders')->where('created_at', '>=', '2026-01-01')->selectRaw('COUNT(*), SUM(amount)')->first();
启用生成器惰性加载:
function readLargeFile($path) {
$handle = fopen($path, 'r');
while (($line = fgets($handle)) !== false) {
yield trim($line);
}
fclose($handle);
}
foreach (readLargeFile('/var/log/app.log') as $line) { processLine($line); }



















