
在日常业务开发中,我们经常会遇到需要处理历史数据的场景:可能是数据迁移、统计报表重构,或者是新功能上线需要对旧数据做预处理。今天,我将分享一个在 Laravel 项目中处理历史订单统计数据的渐进式同步方案。
问题背景
最近在电商项目中,我们需要将所有历史订单数据同步到结算表中,并生成每日统计报表。面临的挑战包括:
- 数据量巨大:订单数据达千万级别,不能一次性处理
 - 业务不能停:同步过程不能影响正常业务运行
 - 准确性要求高:退款等数据需要按实际发生时间统计
 - 可恢复性:任务可能因各种原因中断,需要支持断点续传
 
核心设计思想
1. 渐进式处理
我们采用"分而治之"的策略,将庞大的同步任务分解为按天处理的小任务:
// 从2025-01-01开始,一天一天处理直到昨天
$startDate = '2025-01-01';
$endDate = now()->subDay(); // 处理到昨天
// 每次只处理一个日期的数据
while ($currentDate->lte($endDate)) {
    $this->processDateOrders($currentDate->toDateString());
    $currentDate->addDay();
}
2. 状态持久化
使用缓存记录同步进度,确保任务的可恢复性:
protected function updateProgress($processedDate)
{
    $nextDate = Carbon::parse($processedDate)->addDay()->toDateString();
    
    Cache::put($this->progressCacheKey, [
        'current_date' => $nextDate,
        'last_processed_date' => $processedDate,
        'last_processed_at' => now()->toDateTimeString(),
    ], now()->addDays(30));
}
3. 资源友好设计
在批次处理间加入休眠,避免资源竞争:
// 日期间休眠,避免CPU/内存跑满
if ($this->config['sleep_between_days'] > 0) {
    sleep($this->config['sleep_between_days']);
}
// 及时清理内存,防止内存泄漏
gc_collect_cycles();
4. 时间维度分离
订单数据和退款数据按不同时间维度统计,确保准确性:
// 订单数据按订单创建时间统计
$orderStats = OrderSettlement::whereBetween('order_date', [$startDate, $endDate])->...;
// 退款数据按退款时间统计  
$refundStats = OrderSettlement::whereBetween(DB::raw('DATE(refund_at)'), [$date, $date])->...;
技术实现细节
命令类结构
我们创建了两个主要的 Artisan 命令:
1. 订单结算同步命令
class SyncHistoryOrderSettlement extends Command
{
    protected $signature = 'platform:sync-history-order-settlement';
    // 处理订单数据同步到结算表
}
2. 统计报表生成命令
class SyncHistoryOrderStatistics extends Command
{
    protected $signature = 'platform:sync-history-order-statistics';
    // 根据结算表生成每日统计
}
数据库聚合优化
使用数据库原生聚合函数,避免在应用层处理大量数据:
$stats = OrderSettlement::whereBetween('order_date', [$startDate, $endDate])
    ->select([
        DB::raw('COUNT(*) as order_count'),
        DB::raw('COALESCE(SUM(total_amount), 0) as order_amount'),
        DB::raw('COALESCE(SUM(platform_income), 0) as platform_amount'),
        // ... 更多聚合字段
    ])
    ->first();
进度监控
提供独立的监控命令,实时查看同步进度:
php artisan platform:monitor-order-statistics
📊 订单统计同步进度监控
========================
开始日期: 2025-01-01
结束日期: 2025-06-19
当前处理: 2025-03-15
最后完成: 2025-03-14
总体进度: 74/170 天 (43.53%)
[████████████████████████████████████████░░░░] 43.53%
部署与运行策略
手动执行
# 基本同步(每次处理1天)
php artisan platform:sync-history-order-statistics
# 每次处理多天
php artisan platform:sync-history-order-statistics --days-per-run=5
# 重置进度重新开始
php artisan platform:sync-history-order-statistics --reset
自动化部署
在 App\Console\Kernel 中配置定时任务:
protected function schedule(Schedule $schedule)
{
    // 每小时执行一次,持续同步
    $schedule->command('platform:sync-history-order-statistics --days-per-run=1')
             ->hourly()
             ->withoutOverlapping();
}
方案优势
1. 安全性
- 内存安全:按天处理,避免内存溢出
 - 业务安全:休眠机制确保不影响正常业务
 - 数据安全:事务性和一致性保证
 
2. 可观测性
- 实时进度:随时查看处理进度
 - 详细日志:每个步骤都有明确输出
 - 错误追踪:异常情况有完整日志记录
 
3. 灵活性
- 可配置:支持调整处理速度和批次大小
 - 可恢复:支持从任意断点继续
 - 可扩展:易于添加新的统计维度
 
4. 可维护性
- 清晰的责任分离:不同命令处理不同关注点
 - 模块化设计:每个方法职责单一
 - 完善的错误处理:优雅处理各种异常情况