6.0 KiB
6.0 KiB
指标查询性能优化计划
📋 背景分析
当前仪表盘的日汇总查询涉及多个复杂计算:
- ✅ 当天新增换单数
- ✅ 当天应该换单数
- ✅ 当日完成数
- ✅ 24小时完成率
- ✅ 当日完成率
- ✅ 16点前/后统计
- ✅ STOP数、标签推送、扫描数等
当前问题:
- 🐌 单次查询需要 15-30 秒+
- 🔄 涉及多个 JOIN 操作和复杂的时区转换
- 💾 数据量可能较大,无缓存
🎯 优化方案(按优先级)
方案一:数据库视图(强烈推荐)⭐⭐⭐⭐⭐
优点:
- ✅ 预聚合数据,查询极快(通常 < 1 秒)
- ✅ 减少应用端计算,降低 CPU 占用
- ✅ 支持直接在 SQL 中添加索引
- ✅ 易于维护和调试
实现步骤:
- 创建
v_DailyMetricsSummary视图 - 在视图中实现所有指标计算逻辑
- 后端直接查询视图,返回结果
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 缓存层(中等优先级)⭐⭐⭐⭐
优点:
- ✅ 应用端缓存,极速响应
- ✅ 减轻数据库压力
- ✅ 支持多环境部署
实现步骤:
- 在
MetricsCalculationService中添加缓存装饰 - 设置缓存过期时间(如 5 分钟自动刷新)
- 提供手动刷新接口
代码示例:
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) | 低 | 低 | ⭐⭐ |
🚀 推荐实施方案(综合最优)
第一阶段:快速修复(立即实施)
-
✅ 创建
v_DailyMetricsSummary视图- 预计开发时间:2-3 小时
- 预期性能提升:85-90%
- 难度:中等
-
✅ 优化关键索引
- 预计开发时间:1 小时
- 难度:低
第二阶段:长期优化(后续迭代)
-
🔄 添加 Redis 缓存层
- 前提:视图已完成
- 预计开发时间:2-3 小时
-
📊 监控和调优
- 定期检查查询性能
- 根据实际使用情况调整缓存策略
💡 技术实现建议
视图设计核心逻辑
时区处理:
-- 使用 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 |
🎓 关键要点
- 视图是首选 - 一次性投入,长期收益
- 缓存是补充 - 结合视图,性能最优
- 索引是基础 - 无论采用哪种方案都需要
- 监控很关键 - 定期检查性能指标
📝 后续步骤
- 用户确认优化方案
- 创建数据库视图
- 修改后端代码以调用视图
- 性能测试和验证
- 部署上线