本文目录导读:

在Laravel中处理大量数据时,内存优化非常重要,以下是使用游标查询进行内存优化的详细方案:
基础游标查询
使用 cursor() 方法
use App\Models\User;
// 传统方式(内存消耗大)
$users = User::where('status', 'active')->get(); // 一次性加载所有数据
// 游标方式(内存友好)
foreach (User::where('status', 'active')->cursor() as $user) {
// 每次只加载一条记录到内存
processUser($user);
}
带条件的高级游标
// 添加条件、排序和限制
$query = User::where('status', 'active')
->where('created_at', '>', now()->subDays(30))
->orderBy('id')
->take(100000);
foreach ($query->cursor() as $user) {
// 处理每条记录
echo $user->name;
}
批量游标处理
使用 chunkById() 分批处理
// 按ID分批处理,避免游标长时间占用连接
User::where('status', 'active')
->orderBy('id')
->chunkById(1000, function ($users) {
foreach ($users as $user) {
processUser($user);
}
});
自定义批量处理
// 手动控制批次大小
$batchSize = 500;
$lastId = 0;
do {
$users = User::where('id', '>', $lastId)
->where('status', 'active')
->orderBy('id')
->limit($batchSize)
->get();
foreach ($users as $user) {
processUser($user);
}
$lastId = $users->last()->id ?? 0;
} while ($users->count() >= $batchSize);
高级内存优化技巧
结合集合操作
// 使用cursor并配合集合筛选
User::where('active', true)
->cursor()
->filter(function ($user) {
return $user->age > 18;
})
->each(function ($user) {
// 处理符合条件的用户
});
// 使用map进行数据转换
$emails = User::cursor()
->map(function ($user) {
return [
'id' => $user->id,
'email' => $user->email
];
});
内存监控和释放
use Illuminate\Support\Facades\Log;
// 监控内存使用
$memoryStart = memory_get_usage(true);
foreach (User::cursor() as $user) {
processUser($user);
// 定期检查内存
if (memory_get_usage(true) - $memoryStart > 50000) { // 50MB
Log::warning('High memory usage detected', [
'memory_used' => memory_get_usage(true) / 1024 / 1024 . 'MB',
'processed' => $processed ?? 0
]);
// 强制垃圾回收
gc_collect_cycles();
}
$processed++;
}
$memoryEnd = memory_get_usage(true);
Log::info('Cursor processing completed', [
'processed' => $processed,
'peak_memory' => memory_get_peak_usage(true) / 1024 / 1024 . 'MB'
]);
复杂查询优化
复合索引优化
// 确保使用索引的查询
$query = User::where('status', 'active')
->where('created_at', '>=', now()->subMonth())
->select(['id', 'name', 'email']) // 只选择需要的字段
->orderBy('id')
->cursor();
// 添加索引
// php artisan make:migration add_composite_index_to_users
Schema::table('users', function (Blueprint $table) {
$table->index(['status', 'created_at', 'id']);
});
使用原始查询
use Illuminate\Support\Facades\DB;
// 使用原生SQL和游标
DB::table('users')
->select('id', 'name', 'email')
->whereRaw('status = ?', ['active'])
->orderBy('id')
->cursor();
大数据集导出
CSV导出优化
function exportUsersToCSV($filename) {
$handle = fopen($filename, 'w');
// 添加表头
fputcsv($handle, ['ID', 'Name', 'Email', 'Created At']);
User::query()
->select(['id', 'name', 'email', 'created_at'])
->where('status', 'active')
->orderBy('id')
->cursor()
->each(function ($user) use ($handle) {
fputcsv($handle, [
$user->id,
$user->name,
$user->email,
$user->created_at
]);
});
fclose($handle);
return "Export completed!";
}
内存设置和配置
设置PHP内存限制
// 在服务提供者或引导文件中
ini_set('memory_limit', '256M'); // 不要设置太大,根据需要调整
// 或者按需动态设置
class UserExportService
{
public function exportLargeDataset()
{
$oldMemoryLimit = ini_get('memory_limit');
ini_set('memory_limit', '512M');
try {
// 执行大规模操作
$this->processData();
} finally {
// 恢复原始设置
ini_set('memory_limit', $oldMemoryLimit);
}
}
}
配置连接参数
// config/database.php
'migrations' => [
'table' => 'migrations',
'update_date_on_publish' => true,
],
// 允许MySQL使用无缓冲查询
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false,
PDO::ATTR_EMULATE_PREPARES => false,
]) : [],
完整示例
<?php
namespace App\Services;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class UserDataProcessor
{
protected int $batchSize = 1000;
protected int $processCount = 0;
protected float $startTime;
protected int $startMemory;
public function __construct()
{
$this->startTime = microtime(true);
$this->startMemory = memory_get_usage(true);
}
public function processActiveUsers(): void
{
$this->logProgress('Starting user processing');
// 使用cursor处理所有活跃用户
User::query()
->select(['id', 'name', 'email', 'attributes'])
->where('status', 'active')
->whereNotNull('email')
->orderBy('id')
->cursor()
->each(function ($user) {
$this->processSingleUser($user);
$this->trackProgress();
});
$this->logProgress('Processing completed');
}
protected function processSingleUser(User $user): void
{
// 处理单个用户逻辑
try {
// 示例:更新用户状态或发送通知
// $this->updateUserData($user);
} catch (\Exception $e) {
Log::error('User processing failed', [
'user_id' => $user->id,
'error' => $e->getMessage()
]);
}
}
protected function trackProgress(): void
{
$this->processCount++;
// 每1000条记录输出一次进度
if ($this->processCount % 1000 === 0) {
$elapsedTime = microtime(true) - $this->startTime;
$currentMemory = memory_get_usage(true);
Log::info('Progress update', [
'processed' => $this->processCount,
'time_elapsed' => round($elapsedTime, 2) . 's',
'memory_used' => round($currentMemory / 1024 / 1024, 2) . 'MB'
]);
// 触发垃圾回收
gc_collect_cycles();
}
}
protected function logProgress(string $message): void
{
Log::info($message, [
'memory_used' => round(memory_get_usage(true) / 1024 / 1024, 2) . 'MB',
'peak_memory' => round(memory_get_peak_usage(true) / 1024 / 1024, 2) . 'MB',
'time_elapsed' => round(microtime(true) - $this->startTime, 2) . 's'
]);
}
}
性能监控和优化
监控查询性能
// 启用查询日志
DB::enableQueryLog();
// 执行查询
$users = User::where('status', 'active')->cursor()->take(10);
// 查看查询日志
dump(DB::getQueryLog());
使用 EXPLAIN 优化
// 检查查询计划
$explainResult = DB::table('users')
->select('id', 'name')
->where('status', 'active')
->orderBy('id')
->explain();
dd($explainResult);
最佳实践建议
- 使用
select()只选择需要的字段 - 适当调整批次大小(1000-2000通常比较好)
- 监控内存使用情况
- 设置合理的数据库索引
- 避免在循环中进行复杂操作
- 使用日志记录处理进度
- 考虑使用队列异步处理
这些方法可以显著降低内存使用,特别是在处理几十万到数百万条记录时,选择合适的方法取决于你的具体场景和数据量。