MySQL 如何保存有顺序的列表?

2021-05-24 12:44:08 +08:00
 phony2r

有一个表表示一个列表的每一小项 id/name/order, 其中 order 表示小项在列表中的顺序

现在这个列表是可以拖动进行排序的, 这样的话每次排序都会产生大量的 order 变化, 比如 1 移到 4, 就会产生 2->1 / 3->2 / 4->3 / 1->4 一共 4 个 order 变化

类似的, 加入 1 移到 100 的话就会产生 100 个 order 变化

如果批量更新的话, MySQL 的性能是个问题, 而且也无法保证原子性(部分更新 order 成功部分失败)

像这种带顺序的列表应该怎么设计?

4151 次点击
所在节点    MySQL
23 条回复
wangritian
2021-05-24 13:00:45 +08:00
我会选择全量更新,一个 varchar 字段保存整个列表的表头,用 json 数组或逗号隔开
renmu123
2021-05-24 13:02:56 +08:00
设计 order 为 0,10000,20000 如果有变化,就设置 order 为前后两个 order 的一半
yeqizhang
2021-05-24 13:06:15 +08:00
放一个事务里没有你说的原子性问题。

你从 1 拖到 100 拖到很长了,大量数据要求拖拽排序的业务场景很难想象是什么,像信息流之类的不如给个权重设置。

有请后面大佬给出方案
phony2r
2021-05-24 13:10:30 +08:00
@wangritian 没理解
phony2r
2021-05-24 13:11:00 +08:00
@renmu123 这样设计的话更新次数有限制 达到一定次数就失效了
phony2r
2021-05-24 13:12:09 +08:00
@yeqizhang 比如歌单里面音乐的顺序
SorcererXW
2021-05-24 13:14:03 +08:00
可以用联合索引( order + last_order_timestamp )排序

1->4 的话, 就把 1 的 order 字段更新为 4, last_order_timestamp 设置为当前时间戳
排序的时候优先按 order 升序, 再按 last_order_timestamp 降序排

这样每次更新只需要更新一行
renmu123
2021-05-24 13:18:50 +08:00
@phony2r 为什么会失效?小数点也可以比大小啊
timethinker
2021-05-24 13:21:16 +08:00
取决于是读多还是写多,另外跟数据量也有关系,假如说按照最坏的情况,修改序列会导致上 W 条的记录被修改,那么单独维护一张表存储序列是一个不错的方案,里面的内容是一个字符串,用分隔符组装 ID 列表,这样在变更序列的时候仅需要修改这一条数据(一对多,这里序列表 ID 就是主表的 ID,比如歌单 ID,评论文章 ID,小说 ID 等等)。

不过在读取的时候可能会遇到条件过滤+分页的问题,还是取决于场景,可以在更新序列表后异步的更新表数据( order ),此时数据表上的 order 字段只是一个冗余,在修改过后可能会遇到延迟的情况,在关键的地方还是要从序列表当中取出正确的排序,不过大多数情况下是可以接受的。
yeqizhang
2021-05-24 13:27:01 +08:00
我想了下,觉得全量没毛病的,其实全量也是更新一个区间的数据,具体多少就是拖拽了多少个位置,性能上问题也不是很大,限制一下一次最多能拖多长就行
lujjjh
2021-05-24 13:28:29 +08:00
https://www.zhihu.com/question/55789722/answer/146650607

teambition 是类似 #2 的做法,但是有极小概率会 fallback 到 slow path (整个分组任务重算)。
jorneyr
2021-05-24 13:29:23 +08:00
我们是用浮点数表示顺序 (初始赋值为创建时候的时间戳),2 个浮点数求平均值就可以了。
est
2021-05-24 13:33:50 +08:00
> 现在这个列表是可以拖动进行排序的, 这样的话每次排序都会产生大量的 order 变化, 比如 1 移到 4, 就会产生 2->1 / 3->2 / 4->3 / 1->4 一共 4 个 order 变化


保存为 float 。23333

1 2 3 4 把 1 移动到 4,改成
2 3 4 4.1
tairan2006
2021-05-24 13:35:07 +08:00
如果列表很小的话:设 order 是一个 bigint 字段,前端传入 front 节点,令拖动节点 order=front+1,并 update 所有 order>front 的 order=order+1

如果列表比较大:设 order 是一个 decimal 字段,插入时两个间隔大一点,拖动后取 order=(front+after)/2 。日取其半,万世不竭(才怪,会达到精度上限)
yeqizhang
2021-05-24 13:35:52 +08:00
@est 太大了,拖不了几次,得是 4.0000001🐶
NULL2020
2021-05-24 14:03:29 +08:00
改用 redis list,前端每次更新都全量发过来,后端直接保存就行了
neoblackcap
2021-05-24 14:13:29 +08:00
实际上这需求我觉得传统的关系型数据库不好搞。你用一个内存数据库作为缓存层,定时将结果全量同步回数据库就可以了。
no1xsyzy
2021-05-24 14:24:33 +08:00
@SorcererXW 时间这个想法很好,但想到一个问题(以下表示为 元素=(order, time))
a=(1,0) b=(2,0) c=(3,0) d=(4,0)
d 移到 ab 之间
a=(1,0) d=(2,1) b=(2,0) c=(3,0)
然后尝试 c 移到 db 之间,那样就无法正确找到这个间隔了。
love
2021-05-24 15:36:28 +08:00
有一个方法很好用,就是另建一个表保存列表顺序,就一个 varchar 字段,里面是那个有序列表的 id 列表,用逗号分隔
这样只要保存一条记录就可
akira
2021-05-25 03:14:19 +08:00
对连续的数据进行位置调整,要变动最小的话,那可以考虑链表呀。

例如 表结构改为 id/name/pre-id ,这样你每次调整排序,最多只需要改 2 个数了。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/778830

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX