mongodb排序(聚合)超出内存限制
前情摘要
在某次开发过程中,有一个页面查询前几页的数据都能够正常返回,但是当查询最后一页时发现后台抛出了异常,详细信息如下:
Caused by: com.mongodb.MongoQueryException: Query failed with error code 96 and error message 'Executor error during find command :: caused by :: errmsg: "Sort operation used more than the maximum 33554432 bytes of RAM. Add an index, or specify a smaller limit."' on server 192.168.0.130:27017
错误分析
查看错误信息我们可以发现,more than the maximum 33554432 bytes of RAM,即排序操作超过了MongoDB单个Session排序可使用的最大内存限制。
官方文档给出的解释:
In MongoDB, sort operations can obtain the sort order by retrieving documents based on the ordering in an index. If the query planner cannot obtain the sort order from an index, it will sort the results in memory. Sort operations that use an index often have better performance than those that do not use an index. In addition, sort operations that do not use an index will abort when they use 32 megabytes of memory.
文档中意思大概是:在排序字段未利用到索引的情况下,若超过32M内存则会被Abort,语句直接返回报错。那么解决问题的方法很简单,在错误信息提也提示了, Add an index, or specify a smaller limit.,我们只需要给要排序的字段添加上索引或者把需要排序的数据限制的更小就行了。
但是,业务程序中查询一般都是使用聚合查询方法 aggregate(),对于聚合查询中的Sort Stage,官方文档说明了使用内存排序能使用最大的内存为 100M,要执行大型排序,需要启用allowDiskUse=true选项使用系统缓存将数据写入临时文件以进行排序。来避免报错。
官方文档见:https://docs.mongodb.com/manual/reference/operator/aggregation/sort/#sort-memory-limit
解决过程
非聚合查询
我们只需要给要排序的字段添加上索引或者把需要排序的数据限制的更小就行了。
Java聚合查询中对allowDiskUse设置方法:
对于Spring Data MongoDB 1.5.x以上2.0版本以下版本并没有提供显式的封装 , 故而需要自己创建:
AggregationOptions aggregationOptions = new AggregationOptions(true, false, null);
2.0及以上版本提供内部静态类,以builder的方式设置:
AggregationOptions aggregationOptions = AggregationOptions.builder().allowDiskUse(true).build();
然后在Aggregation.newAggregation之后加上.withOptions(aggregationOptions)
// 对于Spring Data MongoDB 1.5.x以上2.0版本以下版本并没有提供显式的封装 , 故而需要自己创建:
AggregationOptions aggregationOptions = new AggregationOptions(true, false, null);
// 2.0及以上版本提供内部静态类,以builder的方式设置:
AggregationOptions aggregationOptions = AggregationOptions.builder().allowDiskUse(true).build();
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.match(criteria),
Aggregation.sort(new Sort(new Sort.Order(direction, property))),
Aggregation.skip((long) page),
Aggregation.limit(size)
).withOptions(aggregationOptions); // 在此处运用配置
List<OrderItem> orderItems = mongoTemplate.aggregate(aggregation, "mro_orders_items", OrderItem.class).getMappedResults();
long count = mongoTemplate.aggregate(aggregation, "mro_orders_items", OrderItem.class).getMappedResults().size();
Mongo原生聚合函数对allowDiskUse设置方法
在以JAVA代码方式解决后,又去琢磨了一下原生函数的处理方法
db.getCollection("mro_orders_items").aggregate([
{
$project: {
_id: "$orderId"
}
},
{
$skip: 0
},
{
$limit: 100
},
{
$sort: {
createAt: 1
}
}
], {
allowDiskUse: true
})