【问题标题】:How to store ordered items which often change position in DB如何存储经常在数据库中更改位置的订购项目
【发布时间】:2011-03-24 21:03:18
【问题描述】:

我需要能够在数据库中存储大量订购项目。到目前为止,这是直截了当的:

ID Position OtherFields
 1     45      ...
 2   4736      ...
 3    514      ...
 ...

在查询中,我总是需要以正确的顺序获取一些项目(根据 OtherFields 过滤)。也很简单,在位置上放置一个索引并使用“按位置排序”。

现在的问题:项目经常改变它们的位置,而不仅仅是 1 或 2。如果 ID 2 将位置从 4736 更改为 2000,我需要更新它的位置和所有的位置旧位置 2000 和 4735 之间的元素,每行加 1。而且每笔交易改变的不仅仅是一个ID,而是几个,短时间内可以有很多笔交易。

我认为处理 update 问题的最优雅的方法是使用链表而不是 Position 列,在该列中,我可以通过将其前身链接到其后继者来从其旧位置删除 ID 2然后通过在其新的前任和继任者之间链接它来将其插入其他地方。这将是每次位置更改的恒定且少量的更新,这也是我处理更改的首选方式(在我的情况下是 Java)。但是,这会引发以正确顺序进行 查询 的 N+1 问题 - 即使对于少数元素,在最坏的情况下我必须遍历整个列表才能找出它们的正确顺序。

所以我的问题是:为了在必要的更新和查询性能之间取得良好的平衡,您有什么建议?

到目前为止,我看到了两个有希望的方向:

  1. 是否有一个 DBMS(理想情况下是开源的)可以处理链表,不仅具有语法糖,而且具有良好的性能,例如通过使用链接元素的内部索引?

  2. 也许只使用一个 BLOB 来存储整个链接列表也是一种选择!这样的链接列表可以有多大/它在数据库中使用多少内存以及何时获取1.000.000个条目?我正在使用 Java + Hibernate 以防万一。我想在获取 BLOB 之后处理内存中的整个列表应该非常快!?

当然也欢迎其他想法!

【问题讨论】:

  • Bill Karwin 对此有很好的回答,您可能想看看:stackoverflow.com/questions/192220/…
  • 我认为比尔的“树”解决方案不合适。虽然数据是分层的,但它是一个单一的大树路径,每次插入后都必须更新,这相当于重新索引。
  • 我也读过这个问题,起初它对我来说似乎很有希望,但后来我意识到,例如对于总共 2000 个位置 1000 的条目,您必须在 Closure Table 中创建 1000 个后代条目。总的来说,我认为这将为 2000 个职位提供大约 2.000.000 个条目,并进行大量必要的更新——对于一个简单的列表来说,写作方面的工作和性能太多,所以我同意 Marcus。
  • 在软件工程网站上类似:Storing a re-orderable list in a database

标签: sql data-structures recursion indexing linked-list


【解决方案1】:

如果您放宽 Position 列必须包含从 1 到 N 的整数的约束,而是允许它包含任何数字,那么您可以高效地进行搜索和更新。

您可以通过计算平均 (A + B) DIV 2 在位置 A 和 B 的其他两个项目之间插入一个项目。例如,如果 A 是 10000,B 是 12000,那么您的新位置是 11000。有时您会运行由于聚类导致的间隙,此时您可以遍历整个表,更均匀地重新分配位置。

【讨论】:

  • 我只是把它扔在那里。 Position 可以是浮动列吗?您总是可以将一个数字分成两半。 :)
  • @Marcus Adams:可以,但您最终还是会遇到同样的问题,即由于精度和舍入误差而没有间隙。
  • 无论如何想想都很有趣。
  • 这完全有道理,我想我会用 BIGINT 试试。只需阅读无符号它从 0 到 18.446.744.073.709.551.615。即使从 1.000.000 个均匀分布的条目列表开始,这也会在它们之间留下大约... 18.446.744.073.709 个条目的“空间”,在第一次碰撞之前留出一些时间。在最坏的情况下(所有更新总是进入总是分成 2 的最小间隙),在必要的重新分配之前应该是 44 次更新,我认为 (2^44 = 17.592.186.044.416
  • 平均是一个聪明的举动!不幸的是,当您重做分发时...
【解决方案2】:

如果使用小数来表示位置呢?如果你这样做了,你可以使用以下方法将它放在其他位置之间:

原始记录为:

ID    Position  Otherfields
--------------------------
1     1.0
2     2.0
.
.
.
5000  5000.0

然后假设您将 ID 1 移动到 5000 之前

ID    Position  Otherfields
--------------------------
1     4999.9
2     2.0
.
.
.
5000  5000.0

现在假设您要将 ID 2 放在 1 到 5000 之间:

ID    Position  Otherfields
--------------------------
1     4999.9
2     4999.91
.
.
.
5000  5000.0

这样你只改变一个记录...

更新:

重新阅读@Mark Byers 的建议后,我们的解决方案似乎非常相似,尽管使用小数对我来说似乎要简单得多...

【讨论】:

  • 你重新发明了杜威十进制。
【解决方案3】:

另一种解决方案是使用词汇排名。每当两个字符串之间没有值时,只需添加一个新字符。唯一的缺点是,与其他答案中提到的数值解相比,它消耗更多的内存。

规范化对于无错误的数据库不是必需的,但可以选择性地进行以减少字符串长度。

这是一个有趣的视频,详细解释:https://www.youtube.com/watch?v=OjQv9xMoFbg&feature=youtu.be

【讨论】:

    【解决方案4】:

    以下内容可能会有所帮助。它不会直接回答您的问题,但可以说明如何执行此操作(如果可能满足您的要求):

    ranking entries in mysql table

    【讨论】:

    • 这最好作为评论,而不是答案。
    猜你喜欢
    • 2012-06-21
    • 2018-10-13
    • 2012-06-08
    • 1970-01-01
    • 1970-01-01
    • 2012-07-08
    • 1970-01-01
    • 2012-12-18
    • 1970-01-01
    相关资源
    最近更新 更多