如何提升mongodb中group的效率
发布于 7 年前 作者 mehunk 6225 次浏览 来自 问答

我的应用类似于一个手机号码缴费的系统,根据手机号码的缴费订单之前销售的代理商进行返利。 当前的表结构如下: users: // 用户表,当前记录150 条 { _id: ObjectId; acceptOrder: [ObjectId]; // 订单 id }

orders: // 号码订单表,当前记录 600 条
{
	_id: ObjectId;
	haveNo: [ObjectId]; // 号码 id
}

rechargeOrders: // 充值订单表,当前记录 20k 条 
{
	_id: ObjectId;
	_simcardNo: ObjectId; // 号码 id
	fee: Number; // 充值金额
	createdAt: Date; // 充值时间
	paid: Boolean; // 话费是否已经支付
	rebatePaid: Boolean; // 返利是否已经支付
}

当前需要按照用户的维度统计应结算的返利,代码(删减版)如下:

db.users.aggregate([
	{$unwind: "$acceptOrder"},
	{$project: {_id: "$_id", acceptOrder: "$acceptOrder"}},
	{$lookup: {
		from: "orders",
		localField: "acceptOrder",
		foreignField: "_id",
		as: "orders"
	}},
	{$unwind: "$orders"},
	{$project: {_id: "$_id", haveNo: "$orders.haveNo"}},
	{$unwind: "$haveNo"},
	{$lookup: {
		from: "rechargeorders",
		localField: "haveNo",
		foreignField: "_simcardId",
		as: "rechargeOrders"
	}},
	{$unwind: "$rechargeOrders"},
	{$match: { "rechargeOrders.paid": true, "rechargeOrders.createdAt": {$lt: ISODate("2017-01-11T00:00:00.000+08:00") }}},
	{$project:  {_id: "$_id", fee: "$rechargeOrders.fee"}},
	{$group: {
		_id: {_id: "$_id", company: "$company"},
		fee: {$sum: "$fee"},
		count: {$sum: 1}
	}}
])

当前的数据库版本为3.4.1。每个表的_id字段和一对多的数组都设置了索引。当前出现的问题是,在这样的数量级的情况下,使用上面的语句查询起来需要1700s左右。经过分析,瓶颈应该是在加上$group这句之后,在$group之前的语句最多只需要3s的时间。实在不知道应该如何优化?

4 回复

。。。。。。同学你这pipeline太多了 看看能不能合并的 另外看看是否命中索引

@jiangzhuo 我也觉得pipeline的确太多了,但是好像业务逻辑就这样,用户->订单->号码->充值订单,我这还砍了一个号码的关联过程……莫非数据库不应该这么设计……

@mehunk 没有记错的话mongodb的 group 操作对索引的运用是比较差的 那么针对你这个业务场景,我的建议是,用$group之前的操作,加上一个以id为依据的排序,然后用返回的索引,自己维护一个map/array,在比对 cursor.next()中获取到的id之后,增加map/arraidy中对应的sum值。以你的情况来看的话,需要维护的内存是非常小的。

@aojiaotage 非常感谢你这么耐心细致的解答。我一会就按照你的方法试一下。不过就是觉得mongo既然提供了group,还需要自己用代码去实现有点别扭。 另外,我昨天在测试的过程中还发现了一个问题,下面这句,如果paid的条件是true的话,速度就非常快(3s以内),但是如果修改为false,速度就非常慢(具体的时间还会根据后面的createdAt的条件变化,但是基本上会慢10倍以上)。我给这个paid值加索引也丝毫不影响查询速度,通过开启explain查看,发现后面的match语句都没有使用索引,不知道为什么……

	{$match: { "rechargeOrders.paid": true, "rechargeOrders.createdAt": {$lt: ISODate("2017-01-11T00:00:00.000+08:00") }}},
回到顶部