Files
LabelChange-server/.trae/documents/metrics_query_performance_optimization_plan.md
2026-06-01 16:30:29 +08:00

6.0 KiB
Raw Blame History

指标查询性能优化计划

📋 背景分析

当前仪表盘的日汇总查询涉及多个复杂计算

  • 当天新增换单数
  • 当天应该换单数
  • 当日完成数
  • 24小时完成率
  • 当日完成率
  • 16点前/后统计
  • STOP数、标签推送、扫描数等

当前问题

  • 🐌 单次查询需要 15-30 秒+
  • 🔄 涉及多个 JOIN 操作和复杂的时区转换
  • 💾 数据量可能较大,无缓存

🎯 优化方案(按优先级)

方案一:数据库视图(强烈推荐)

优点

  • 预聚合数据,查询极快(通常 < 1 秒)
  • 减少应用端计算,降低 CPU 占用
  • 支持直接在 SQL 中添加索引
  • 易于维护和调试

实现步骤

  1. 创建 v_DailyMetricsSummary 视图
  2. 在视图中实现所有指标计算逻辑
  3. 后端直接查询视图,返回结果

SQL 视图示例结构

CREATE VIEW v_DailyMetricsSummary AS
SELECT 
    CAST(CONVERT_TZ(r.CreatedAt, '+00:00', '-05:00') AS DATE) AS MetricsDate,
    COUNT(DISTINCT CASE WHEN ... THEN r.Id END) AS DailyNewReplaceCount,
    COUNT(DISTINCT CASE WHEN ... THEN r.Id END) AS DailyShouldReplaceCount,
    COUNT(DISTINCT CASE WHEN ... THEN r.Id END) AS DailySuccessCount,
    ...
FROM LabelReplace r
LEFT JOIN LabelScan s ON r.NeutralWaybillNumber = s.NeutralWaybillNumber
WHERE CONVERT_TZ(r.CreatedAt, '+00:00', '-05:00') >= CURRENT_DATE
GROUP BY MetricsDate

预期效果:查询时间从 15-30 秒 → < 1 秒


方案二Redis 缓存层(中等优先级)

优点

  • 应用端缓存,极速响应
  • 减轻数据库压力
  • 支持多环境部署

实现步骤

  1. MetricsCalculationService 中添加缓存装饰
  2. 设置缓存过期时间(如 5 分钟自动刷新)
  3. 提供手动刷新接口

代码示例

public async Task<Daily24HCompletionRateDto> GetDailySummaryAsync(DateTime date)
{
    var cacheKey = $"metrics:daily:{date:yyyy-MM-dd}";
    
    // 尝试从缓存获取
    if (_cacheService.TryGet(cacheKey, out var cachedData))
    {
        return cachedData;
    }
    
    // 从数据库查询
    var summary = await CalculateFromDatabase(date);
    
    // 缓存 5 分钟
    _cacheService.Set(cacheKey, summary, TimeSpan.FromMinutes(5));
    
    return summary;
}

预期效果:第一次查询 15-30 秒 → 后续查询 < 100 ms


方案三SQL 存储过程(降低优先级)

优点

  • 数据库端执行,性能接近视图
  • 更灵活的逻辑控制
  • 易于版本管理

缺点

  • 维护复杂度高
  • 跨平台迁移困难

适用场景

  • 如果后续需要非常复杂的业务逻辑
  • 需要多步骤的数据验证

方案四:数据库索引优化(基础优化)

必需的索引

-- 优化时区转换查询
CREATE INDEX idx_created_at ON LabelReplace(CreatedAt);

-- 优化 JOIN 操作
CREATE INDEX idx_neutral_waybill ON LabelScan(NeutralWaybillNumber);
CREATE INDEX idx_neutral_waybill_replace ON LabelReplace(NeutralWaybillNumber);

-- 优化状态查询
CREATE INDEX idx_replace_status ON LabelReplace(Status);
CREATE INDEX idx_scan_result ON LabelScan(Result);

预期效果:基础查询优化 15-20% 性能提升


📊 方案对比表

方案 查询速度 实现难度 维护成本 推荐指数
视图 (< 1s)
Redis缓存 (< 100ms)
存储过程 (1-2s)
索引优化 (10-12s)

🚀 推荐实施方案(综合最优)

第一阶段:快速修复(立即实施)

  1. 创建 v_DailyMetricsSummary 视图

    • 预计开发时间2-3 小时
    • 预期性能提升:85-90%
    • 难度:中等
  2. 优化关键索引

    • 预计开发时间1 小时
    • 难度:低

第二阶段:长期优化(后续迭代)

  1. 🔄 添加 Redis 缓存层

    • 前提:视图已完成
    • 预计开发时间2-3 小时
  2. 📊 监控和调优

    • 定期检查查询性能
    • 根据实际使用情况调整缓存策略

💡 技术实现建议

视图设计核心逻辑

时区处理

-- 使用 CONVERT_TZ 确保 UTC-5 时区的日期分组
DATE(CONVERT_TZ(r.CreatedAt, '+00:00', '-05:00')) AS MetricsDate

标签率计算

-- 两阶段计算:未扫描状态 vs 已扫描状态
CASE 
    WHEN s.Id IS NULL THEN 
        CASE WHEN r.Label IS NOT NULL THEN 1 ELSE 0 END
    ELSE 
        CASE WHEN s.Result = 0 THEN 1 ELSE 0 END
END AS IsCompleted

分时段统计

-- 16点前/后分组
CASE 
    WHEN HOUR(CONVERT_TZ(r.ReceiptTime, '+00:00', '-05:00')) < 16 
    THEN '16点前' 
    ELSE '16点后' 
END AS TimeSegment

实施检查清单

  • 分析当前查询性能瓶颈(获取执行计划)
  • 创建 v_DailyMetricsSummary 视图
  • 添加必需的数据库索引
  • 修改 GetDailySummaryAsync 方法调用视图
  • 测试新查询性能(对标 < 1 秒)
  • 更新单元测试
  • 性能基准测试和监控
  • 文档更新

📈 预期收益

指标 当前 优化后 提升幅度
查询延迟 15-30s < 1s 95%↓
数据库 CPU 70%↓
用户体验 显著
可扩展性 有限 3-5x

🎓 关键要点

  1. 视图是首选 - 一次性投入,长期收益
  2. 缓存是补充 - 结合视图,性能最优
  3. 索引是基础 - 无论采用哪种方案都需要
  4. 监控很关键 - 定期检查性能指标

📝 后续步骤

  1. 用户确认优化方案
  2. 创建数据库视图
  3. 修改后端代码以调用视图
  4. 性能测试和验证
  5. 部署上线