You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MongoDB小时级价格数据按区间计算涨跌幅的聚合优化方案咨询

现有实现的核心问题

你当前的方案存在两个明显的缺陷,也是性能差、稳定性低的核心原因:

  • 完全依赖history数组的下标取值,要求数组严格按时间倒序、无任何漏采/补采的小时数据,只要出现一次数据缺失,所有区间的涨跌幅计算都会出错
  • 查询时会返回整个history数组的全量数据,如果你存储了1年以上的小时数据,单条文档的history数组就会有近万条元素,网络传输、内存解析的开销都非常大
基于Aggregate聚合的优化方案

完全可以通过MongoDB的聚合管道实现需求,所有计算逻辑在数据库层完成,不需要返回全量history数组,同时按时间维度匹配,完全不依赖数组下标,准确率和性能都远高于现有实现。

实现代码

exports.getCollectibleChangeSummary = async (req, res) => {
  const slug = req.params.slug;
  // 定义各时间区间对应的毫秒数
  const TIME_RANGES = {
    one_day: 24 * 60 * 60 * 1000,
    one_week: 7 * 24 * 60 * 60 * 1000,
    one_month: 30 * 24 * 60 * 60 * 1000,
    three_month: 90 * 24 * 60 * 60 * 1000,
    six_month: 180 * 24 * 60 * 60 * 1000,
    one_year: 365 * 24 * 60 * 60 * 1000
  };
  try {
    const result = await MarketPriceHistoric.aggregate([
      // 第一步:匹配目标商品,建议给collectibleId字段建索引,大幅提升查询速度
      { $match: { collectibleId: slug } },
      // 第二步:提取当前价格(history数组第一条为最新价格)
      {
        $addFields: {
          currentPrice: { $ifNull: [{ $first: "$history.value" }, 0] }
        }
      },
      // 第三步:计算各时间区间的基准价格
      {
        $project: {
          currentPrice: 1,
          // 通用逻辑:过滤出对应时间点及之后的所有数据,取最后一条即为最接近时间点的价格
          one_day_price: {
            $last: {
              $filter: {
                input: "$history",
                cond: { $gte: ["$$this.date", { $subtract: ["$$NOW", TIME_RANGES.one_day] }] }
              }
            }
          },
          one_week_price: {
            $last: {
              $filter: {
                input: "$history",
                cond: { $gte: ["$$this.date", { $subtract: ["$$NOW", TIME_RANGES.one_week] }] }
              }
            }
          },
          one_month_price: {
            $last: {
              $filter: {
                input: "$history",
                cond: { $gte: ["$$this.date", { $subtract: ["$$NOW", TIME_RANGES.one_month] }] }
              }
            }
          },
          three_month_price: {
            $last: {
              $filter: {
                input: "$history",
                cond: { $gte: ["$$this.date", { $subtract: ["$$NOW", TIME_RANGES.three_month] }] }
              }
            }
          },
          six_month_price: {
            $last: {
              $filter: {
                input: "$history",
                cond: { $gte: ["$$this.date", { $subtract: ["$$NOW", TIME_RANGES.six_month] }] }
              }
            }
          },
          one_year_price: {
            $last: {
              $filter: {
                input: "$history",
                cond: { $gte: ["$$this.date", { $subtract: ["$$NOW", TIME_RANGES.one_year] }] }
              }
            }
          }
        }
      },
      // 第四步:计算涨跌幅,无对应数据返回null
      {
        $project: {
          currentPrice: 1,
          one_day_change: {
            $cond: {
              if: { $gt: ["$one_day_price.value", 0] },
              then: { $multiply: [{ $divide: [{ $subtract: ["$currentPrice", "$one_day_price.value"] }, "$one_day_price.value"] }, 100] },
              else: null
            }
          },
          one_week_change: {
            $cond: {
              if: { $gt: ["$one_week_price.value", 0] },
              then: { $multiply: [{ $divide: [{ $subtract: ["$currentPrice", "$one_week_price.value"] }, "$one_week_price.value"] }, 100] },
              else: null
            }
          },
          one_month_change: {
            $cond: {
              if: { $gt: ["$one_month_price.value", 0] },
              then: { $multiply: [{ $divide: [{ $subtract: ["$currentPrice", "$one_month_price.value"] }, "$one_month_price.value"] }, 100] },
              else: null
            }
          },
          three_month_change: {
            $cond: {
              if: { $gt: ["$three_month_price.value", 0] },
              then: { $multiply: [{ $divide: [{ $subtract: ["$currentPrice", "$three_month_price.value"] }, "$three_month_price.value"] }, 100] },
              else: null
            }
          },
          six_month_change: {
            $cond: {
              if: { $gt: ["$six_month_price.value", 0] },
              then: { $multiply: [{ $divide: [{ $subtract: ["$currentPrice", "$six_month_price.value"] }, "$six_month_price.value"] }, 100] },
              else: null
            }
          },
          one_year_change: {
            $cond: {
              if: { $gt: ["$one_year_price.value", 0] },
              then: { $multiply: [{ $divide: [{ $subtract: ["$currentPrice", "$one_year_price.value"] }, "$one_year_price.value"] }, 100] },
              else: null
            }
          }
        }
      }
    ]);
    if (!result.length) {
      return res.status(404).json({ error: "未找到对应商品的价格数据" });
    }
    // 去掉聚合返回的_id字段
    const { _id, ...rest } = result[0];
    res.json(rest);
  } catch (err) {
    return res.status(400).json({ error: errorHandler(err) });
  }
};

额外优化建议

  • 请务必给collectibleId字段添加单键索引,$match阶段命中索引后查询耗时会降到毫秒级
  • 如果你的业务并发量很高,可以加定时任务每天预计算所有商品的各区间涨跌幅,存入单独的字段,查询时直接读取即可,性能可以再提升10倍以上
  • 若history数组长度超过1万条,可以考虑做冷热数据分离,超过1年的历史数据归档到单独的集合中,减少主集合的文档大小

内容的提问来源于stack exchange,提问作者alienbuild

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 08:36:00