MongoDB聚合管道性能优化咨询:大数据量查询导出提速
MongoDB聚合管道性能优化方案(除索引外)
1. 重构日期查询,绕开$expr导致的索引失效
当前代码用$expr配合$dateFromString在聚合管道里动态转换日期,这种写法会让createdAt字段的索引完全失效,MongoDB只能全表扫描匹配日期。
优化做法:
在代码层面提前把前端传的日期字符串转成Date对象,直接用日期类型做比较,不用在聚合里做转换:
// 替换原startDateQuery逻辑 if (filters.startDate) { const startDate = new Date(filters.startDate); // 手动处理+05:30时区 startDate.setHours(startDate.getHours() + 5); startDate.setMinutes(startDate.getMinutes() + 30); startDateQuery = { createdAt: { $gte: startDate } }; } // 同理处理endDateQuery if (filters.endDate) { let endDate = new Date(filters.endDate); endDate.setHours(endDate.getHours() + 5); endDate.setMinutes(endDate.getMinutes() + 30); // 加一天实现原逻辑的$lt endDate.setDate(endDate.getDate() + 1); endDateQuery = { createdAt: { $lt: endDate } }; }
改完后createdAt的索引就能正常生效,大幅减少数据扫描量。
2. 调整管道顺序,尽早过滤数据
现在的逻辑是先把所有关联表都查出来,再做二次过滤,等于先拉取大量无关数据再筛选,完全浪费资源。
优化做法:
- 把基于关联表字段的过滤条件,直接嵌入到对应的
$lookup管道里,提前在关联阶段就过滤掉不需要的数据:
比如把商品名称的搜索放到mastercommodities的lookup里:
// 修改商品关联的lookup { $lookup: { from: 'mastercommodities', localField: 'commodityId', foreignField: '_id', as: 'commodityData', pipeline: [ // 提前过滤符合条件的商品 filters.searchByCommodity ? { $match: { name: { $regex: `${filters.searchByCommodity}`, $options: 'i' } } } : {}, { $project: { name: 1 } } ] } }
同时删掉后续$match里的commoditySearchQuery,这样只有符合搜索条件的商品才会被关联,减少后续$unwind和处理的数据量。
- 同理,把变体搜索、CID号搜索中涉及关联表的部分,都嵌入到对应的
$lookup管道内,尽可能早地砍掉无关数据。
3. 优化正则查询的性能
现在大量用不带锚定的$regex(比如只加i选项,没有^前缀),这种查询既用不上索引,还得全表扫描,性能极差。
优化做法:
- 如果业务允许,优先用前缀匹配(比如
^${filters.searchByCommodity}),这样能利用字段的普通索引; - 要是需要不区分大小写的全文搜索,给对应字段建文本索引,用
$text查询替代$regex:
比如给mastercommodities.name建好文本索引后,查询可以改成:
commoditySearchQuery = { $text: { $search: filters.searchByCommodity } };
文本索引的查询效率比无锚定正则高几个量级。
4. 减少不必要的关联和数据传输
- 每个
$lookup的$project只保留后续需要的字段,比如businessunits只取name和businessUnitCode,当前已经做了,但要确保没有多余字段; - 如果某些关联数据没被过滤或投影用到,比如没有对应搜索条件时,可以直接跳过该关联操作,减少IO开销。
5. 分批处理数据,避免内存过载
2-3万条数据一次性加载到内存生成Excel,会占满应用内存,导致卡顿甚至崩溃。
优化做法:
用MongoDB的游标分批取数据,每次取1000条左右,分批写入Excel,降低内存占用:
const cursor = this.qcInspectionModel.aggregate([...]).cursor(); let batch; while ((batch = await cursor.nextBatch(1000))) { // 分批写入Excel文件 }
另外可以换内存占用更低的Excel生成库,或者直接用数据库端导出工具,减少应用层的处理压力。
6. 提前投影,减少管道内数据传输量
现在最后一步才做$project,但可以在第一次$match之后就加一个投影,只保留后续需要的字段,减少管道中传输的数据体积:
// 第一次$match之后添加 { $project: { qcId: 1, createdAt: 1, status: 1, commodityId: 1, commodityVariantId: 1, businessUnitId: 1, createdBy: 1, commodityDetail: 1, location: 1, middlewareStatus: 1 } }
这样后续的关联和操作只处理必要字段,数据量会小很多。
内容的提问来源于stack exchange,提问作者holoyo
相关产品推荐
相关产品推荐

