关于mongoDB使用sort排序引发的生产bug

前言

前段时间,博主邮箱突然收到生产服务器发来一封异常信息通知邮件,打开看到如下信息:

com.mongodb.MongoQueryException: Query failed with error code 96 and error message 'Executor error during find command: OperationFailed: Sort operation used more than the maximum 33554432 bytes of RAM. Add an index, or specify a smaller limit.' on server 10.53.161.95:27017
com.mongodb.operation.FindOperation$1.call(FindOperation.java:521)
com.mongodb.operation.FindOperation$1.call(FindOperation.java:510)
com.mongodb.operation.OperationHelper.withConnectionSource(OperationHelper.java:431)
com.mongodb.operation.OperationHelper.withConnection(OperationHelper.java:404)
com.mongodb.operation.FindOperation.execute(FindOperation.java:510)
com.mongodb.operation.FindOperation.execute(FindOperation.java:81)
com.mongodb.Mongo.execute(Mongo.java:836)
com.mongodb.Mongo$2.execute(Mongo.java:823)
com.mongodb.OperationIterable.iterator(OperationIterable.java:47)
com.mongodb.FindIterableImpl.iterator(FindIterableImpl.java:151)
com.mongodb.FindIterableImpl.iterator(FindIterableImpl.java:37)
java.lang.Iterable.forEach(Iterable.java:74)

意思大概说,排序操作使用了超过32M的内存。建议添加一个索引,或者指定更小的limit。 刚开始看到这个问题,明白什么意思,但不知道为什么这样。于是乎查看了一下官网文档

官网文档如下:

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.

翻译过来说,就是在mongodb中,sort操作能通过索引获得在要排序的documents中的顺序,如果执行计划中没有从索引中获取顺序,它就会在内存中对检索结果进行排序。 使用索引的Sort操作在性能上往往比没有使用索引的sort要好。另外。没有使用索引的sort操作在使用了32M内存的时候会被终止掉(使用内存排序时,限制查询结果默认最多为32M,超过的话查询则会被终止)。

所以 给排序字段上加索引是能解决这个问题的, 另外有没有其他解决方案呢?

有 可以通过直接修改默认内存配置

db.adminCommand({setParameter:1, internalQueryExecMaxBlockingSortBytes:335544320}) #直接扩大10倍。

但不建议这么做。因为随着数据的膨胀,终有一天,查询数据量再大,还要往上加。

添加索引也有一定弊端,因为索引的维护, 会导致插入/更新数据相对较慢。

综上考虑,可以根据实际使用情况采用合适的方法。
但是本博主,更倾向于使用索引(提升检索速度)。

sort 索引说明

单字段

db.records.createIndex( { a: 1 } )

单字段索引简单,就不赘述了,倒排正排都能使用索引

多字段索引

你可以创建一个复合索引去支持通过多个字段的排序

在排序中,你可以使用索引的全部字段/部分字段。但是,这些排序字段出现的顺序必须和索引建立的顺序一致。例如

索引 { a: 1, b: 1 } 支持 sort by { a: 1, b: 1 } 但是不支持 sort by {b: 1, a: 1}

使用复合索引查询时,在sort()中指定全部字段的方向,要么匹配索引中的方向,要么匹配索引中的反方向,例如:

索引 { a: 1, b: -1 } 支持 sort by { a: 1, b: -1 } 和 sort by { a: -1, b: 1 }, 但不支持 { a: -1, b: -1 } 或者 {a: 1, b: 1}

复合索引遵循最左匹配 例如:

db.data.createIndex( { a:1, b: 1, c: 1, d: 1 } )
以下都会走索引
{ a: 1 }
{ a: 1, b: 1 }
{ a: 1, b: 1, c: 1 }

下边这些查询结果也会使用索引

Exampleindex Prefix
db.data.find().sort( { a: 1 } ){ a: 1 }
db.data.find().sort( { a: -1 } ){ a: 1 }
db.data.find().sort( { a: 1, b: 1 } ){ a: 1, b: 1 }
db.data.find().sort( { a: -1, b: -1 } ){ a: 1, b: 1 }
db.data.find().sort( { a: 1, b: 1, c: 1 } ){ a: 1, b: 1, c: 1 }
db.data.find({ a: { $gt: 4 } } ).sort( { a: 1, b: 1 } ){ a: 1, b: 1 }

排序但是不使用索引的前缀子集

在一个没有匹配索引前缀的子集上的查询也能使用这个索引,只要查询条件中包含 equality 条件 这个条件中字段要满足能匹配索引的前缀(不要求查询条件中字段顺序和索引顺序一致),例如:

ExampleIndex Prefix
db.data.find( { a: 5 } ).sort( { b: 1, c: 1 } ){ a: 1 , b: 1, c: 1 }
db.data.find( { b: 3, a: 4 } ).sort( { c: 1 } ){ a: 1, b: 1, c: 1 }
db.data.find( { a: 5, b: { $lt: 3} } ).sort( { b: 1 } ){ a: 1, b: 1 }

在一个查询中没有指定一个equality条件在索引前缀或者完全与索引匹配的话,操作时不能有效的使用索引,甚至永远不走索引 。 例如: 下边的查询指定按 c 排序,但是查询条件中没有包含 equality 条件
({a:’’ , b:’’})

db.data.find( { a: { $gt: 2 } } ).sort( { c: 1 } )
db.data.find( { c: 5 } ).sort( { c: 1 } )

使用排序规则建立索引

字符串比较 使用指定排序规则,查询时也需要指定规则,否则不走索引

db.myColl.createIndex( { category: 1 }, { collation: { locale: “fr” } } )
db.myColl.find( { category: “cafe” } ).collation( { locale: “fr” } ) 这样才能使用索引
db.myColl.find( { category: “cafe” } ) 这只能使用默认的simple binary collator,而不能使用自建的索引

对于前缀不是string,arrays, embedded DOC的复合索引,指定使用不同的排序规则的操作依然能够使用索引去支持比较。

db.myColl.createIndex(
{ score: 1, price: 1, category: 1 },
{ collation: { locale: “fr” } } )

依然使用simple binary collation for string comparisons, can use the index
db.myColl.find( { score: 5 } ).sort( { price: 1 } )
db.myColl.find( { score: 5, price: { $gt: NumberDecimal( “10” ) } } ).sort( { price: 1 } )

坚持原创技术分享,您的支持将鼓励我继续创作!