面试题答案
一键面试实现思路
- 筛选过去一周的订单:使用
$match
阶段,通过比较下单时间字段与当前时间减去一周时间,筛选出过去一周内的订单。 - 按用户ID和商品类别分组:使用
$group
阶段,根据用户ID和商品类别进行分组,并计算每组的订单金额总和与订单数量。 - 计算平均订单金额:在
$group
阶段中,用订单金额总和除以订单数量,得到每个用户在每个商品类别下的平均订单金额。 - 筛选平均订单金额大于100的结果:使用
$match
阶段,过滤出平均订单金额大于100的文档。
聚合管道
[
{
$match: {
// 假设下单时间字段为orderTime,这里计算过去一周的时间
orderTime: {
$gte: new Date(new Date().getTime() - 7 * 24 * 60 * 60 * 1000)
}
}
},
{
$group: {
_id: {
userId: "$userId",
category: "$category"
},
totalAmount: { $sum: "$orderAmount" },
orderCount: { $sum: 1 }
}
},
{
$addFields: {
averageAmount: { $divide: ["$totalAmount", "$orderCount"] }
}
},
{
$match: {
averageAmount: { $gt: 100 }
}
}
]
可能存在的性能瓶颈及优化方案
性能瓶颈:
- 大数据量筛选:如果数据量巨大,
$match
阶段筛选过去一周的订单可能会较慢,因为可能需要全表扫描。 - 分组计算:
$group
阶段对大量数据进行分组和计算,可能会消耗较多内存和CPU资源。
优化方案:
- 索引优化:对
orderTime
字段建立索引,这样在$match
阶段可以利用索引快速筛选出符合条件的订单。 - 分片:如果数据量非常大,可以考虑对集合进行分片,将数据分布在多个节点上,提高并行处理能力,减少单个节点的负载。例如按用户ID进行分片,在分组计算时可以并行处理不同分片的数据。
- 使用内存计算:如果服务器内存足够,可以利用内存计算框架(如Redis)来缓存部分计算结果,减少对MongoDB的直接查询压力。比如先在Redis中统计订单金额总和与订单数量,然后再传递到MongoDB进行最终的平均计算和筛选。