MongoDB中如何高效调整集合内文档的排序顺序
不需要逐个更新所有文档的order字段。连续自增整数的排序字段设计在调整顺序时性能极差,有成熟的低开销方案可以实现排序调整。
你当前使用的连续自增order值设计,只要调整的记录不是移动到列表末尾,就需要批量更新插队位置之后的所有记录值。比如你举的把末尾的C移动到最前面的场景,需要把A、B、C三条记录的order值全部重写;如果列表有1万条记录,把最后一条移到最前面就要更新1万条数据,很容易引发锁冲突、接口超时,数据量越大问题越明显。
1. 带步长的排序值方案(最常用,综合性能最好)
核心思路是初始赋值时给order字段留足间隔,不要用连续值:
- 初始插入文档时,
order值按固定大间隔赋值,比如用64位整数的话按1000、2000、3000...的步长递增,或者直接用双精度浮点数存储排序值。 - 调整顺序时,只需要把被移动文档的
order值设为目标位置前后两个文档order值的中位数即可,全程只需要更新1条文档,不需要改动其他任何记录。
对应你举的调整场景,初始数据按步长赋值如下:
{ name: 'A', order: 1000 } { name: 'B', order: 2000 } { name: 'C', order: 3000 }
要把C移动到A之前,只需要给C的order赋值为A之前的位置对应值(A的order是1000,前面无记录就取比1000小的中位值500),调整后的数据为:
{ name: 'C', order: 500 } { name: 'A', order: 1000 } { name: 'B', order: 2000 }
查询时直接按order升序排序就能拿到正确结果,整个操作只需要1次更新请求。
这个方案唯一的维护成本是:当多次调整后相邻两个文档的order间隔小到无法再取中位值(比如整数场景下相邻值差1,浮点数场景下触达精度上限),再触发一次全列表的order值重整,重新按固定步长给所有文档赋值即可。这类重整操作触发频率极低,99%以上的排序调整场景都不需要执行。
2. 双向链表存储方案(适合超大规模列表)
如果列表规模特别大(比如十万级以上),可以放弃全局排序值,给每个文档增加prev_id、next_id两个字段,用双向链表的结构存储文档的相邻关系。调整顺序时只需要修改移动位置前后最多4个文档的关联指针,不需要批量更新其他记录。
这个方案的缺点是按顺序拉取全量/分页列表的查询复杂度高,需要遍历链表,不适合需要频繁分页查询有序列表的业务场景。
内容的提问来源于stack exchange,提问作者Mike woods

