mongodb 使用聚合aggregate 排序后分页结果顺序不一致问题的解决办法及处理过程 解决数据重复

结论

先上结论,在不建立索引的情况下,除使用 _id 之外的字段排序,使用 find 进行分页查询 skiplimit 的查询结果能正常返回顺序,不会受到其他影响。而使用聚合方式 aggregate 查询,其返回结果是不确定的,解决方式是为这个字段建立索引或者增加能确定顺序的排序条件。
在这里插入图片描述

原因

因为在这种查询中,提供的排序条件是不充分的。在进行聚合查询时,数据库已经按照要求按 {count: 1} 完成了排序,但是因为它们的值都一样,不管谁放在前面谁放在后面其实都没有违反要求,因为查询要求只是 count 的升序而已。从数据库的角度来讲,既然没有额外的要求,那当然是以最高效的方式返回结果,也就是不管 count 以外的顺序,因为这样最省资源。
那么什么是最高效?这里涉及一些数据库底层的知识。在单机上,如果没有索引支持,数据库会尝试遍历所有数据,然后做一个内存排序来返回结果,从节省资源的角度,显然这个排序只排到满足了 count 升序为止,其他字段可以说是先到先得。这就造成在count相同时,其他顺序是随机的。它们可能受到:

  1. 自己在磁盘上的顺序影响,因为这会影响到数据库先遍历到哪条记录。并且要注意,每次更新数据时它们在磁盘上的顺序是会变化的;
  2. 在分片集群环境中,结果还受到哪个片先返回数据的影响。分片环境中的排序是先在各个片排好序,再进行一次合并排序;
  3. 理论上还和数据库使用的排序算法相关;

总之,在 aggregate 查询中,如果不指定,数据库不保证返回结果是确定的。
至于解决方案也已经很明确了,指定一个可以完全确定顺序的排序条件,比如:
{$sort: {count: -1, _id: 1}}
但是需要知道的是,这样会让数据库付出额外的操作来保证第二个排序条件的正确性,在实际使用场景中要根据实际情况判断这是不是真的有意义。
另外的方式是给这个字段建立索引,那么查询出来的结果就会按照索引中的顺序返回。

测试过程:

生成20条测试数据

for (let i = 0 ; i < 10 ; i++){
    db.getCollection('Test').insert({name:String(i), count:NumberInt(0)})
}

结果如下:

db.getCollection('Test').find({})

在这里插入图片描述

find 查询分页的结果

db.getCollection('Test').find({}).sort({count:1}).limit(3)

在这里插入图片描述

db.getCollection('Test').find({}).sort({count:1}).limit(5)

在这里插入图片描述
可以看到返回的结果是确定的,不会因为 skiplimit 而受影响。

aggregate 查询分页的结果

db.getCollection('Test').aggregate([
    {$sort: {count:1}},
    {$limit:3}
])

在这里插入图片描述

db.getCollection('Test').aggregate([
    {$sort: {count:1}},
    {$limit:5}
])

在这里插入图片描述

db.getCollection('Test').aggregate([
    {$sort: {count:1}},
    {$limit:10}
])

在这里插入图片描述

db.getCollection('Test').aggregate([
    {$sort: {count:1}},
    {$skip: 1},
    {$limit:5}
])

在这里插入图片描述
在这里我们可以看到,返回结果是不确定的,会随着 skiplimit 而受影响。
增加一个排序字段,再次进行查询,能看到结果符合预期,不会受到影响。

db.getCollection('Test').aggregate([
    {$sort: {count:1,_id:1}},
    {$limit:3}
])

在这里插入图片描述

创建字段索引

这时候我们给这个创建索引,看看情况怎样

db.getCollection('Test').createIndex({"count":1})

在这里插入图片描述
在这里插入图片描述
再次进行查询,看看结果如何

db.getCollection('Test').aggregate([
    {$sort: {count:1}},
    {$limit:10}
])

在这里插入图片描述
此时已经发现,返回结果是跟 find 查询一致,并且不会受到 skiplimit 的影响。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章