PHP项目Laravel游标查询内存优化

wen PHP项目 5

本文目录导读:

PHP项目Laravel游标查询内存优化

  1. 基础游标查询
  2. 批量游标处理
  3. 高级内存优化技巧
  4. 复杂查询优化
  5. 大数据集导出
  6. 内存设置和配置
  7. 完整示例
  8. 性能监控和优化
  9. 最佳实践建议

在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);

最佳实践建议

  1. 使用 select() 只选择需要的字段
  2. 适当调整批次大小(1000-2000通常比较好)
  3. 监控内存使用情况
  4. 设置合理的数据库索引
  5. 避免在循环中进行复杂操作
  6. 使用日志记录处理进度
  7. 考虑使用队列异步处理

这些方法可以显著降低内存使用,特别是在处理几十万到数百万条记录时,选择合适的方法取决于你的具体场景和数据量。

抱歉,评论功能暂时关闭!