【问题标题】:Best way to maintain an ordered list in PostgreSQL?在 PostgreSQL 中维护有序列表的最佳方法是什么?
【发布时间】:2022-05-06 12:22:22
【问题描述】:

假设我有一个名为 list 的表,其中有类似这样的 items(ids 是随机 uuid):

id  rank  text
--- ----- -----
x   0     Hello
x   1     World
x   2     Foo
x   3     Bar
x   4     Baz

我想保持rank 列总是从0n-1 的属性(n 是行数)---如果客户要求insert 带有@987654330 的项目@,那么pg服务器应该将当前的34分别推送到45

id  rank  text
--- ----- -----
x   0     Hello
x   1     World
x   2     Foo
x   3     New Item!
x   4     Bar
x   5     Baz

我目前的策略是有一个专用的插入函数add_item(item) 扫描整个表,过滤掉排名等于或大于被插入项目的项目,并将这些排名加一。但是,我认为这种方法会遇到各种各样的问题——比如竞态条件。

是否有更标准的做法或更稳健的方法?


注意:排名列完全独立于其余列,插入不是我需要支持的唯一操作。将其视为可排序待办事项列表的后端,用户可以动态添加/删除/重新排序项目。

【问题讨论】:

  • 这可以是计算列吗?排名是如何确定的,是否可以存储一个属性,以便可以按需计算排名?
  • 看起来是个坏主意...为什么存储列表顺序很重要?更标准的方法是只插入未排序的并仅在输出点应用订单。
  • @Glenn 不幸的是,没有。排名完全是任意的,实际上是数据的一部分(用户可能能够即时重新排序列表)。
  • 请告诉我们真正的问题是什么。
  • @TimBiegeleisen 抱歉,我刚刚更新了问题以使我的意图更加明确。

标签: postgresql


【解决方案1】:

逐字逐句执行您的建议可能很困难或根本不可能,但我可以建议一种解决方法。维护一个新列ts,它存储插入记录的时间。然后,将当前时间与记录的其余部分一起插入,即

id  rank  text      ts
--- ----- -----     --------------------
x   0     Hello     2017-12-01 12:34:23
x   1     World     2017-12-03 04:20:01
x   2     Foo       ...
x   3     New Item! 2017-12-12 11:26:32
x   3     Bar       2017-12-10 14:05:43
x   4     Baz       ...

现在我们可以通过查询轻松生成您想要的排序:

SELECT id, rank, text,
    ROW_NUMBER() OVER (ORDER BY rank, ts DESC) new_rank
FROM yourTable;

这将在上面的示例表中生成 0 到 5 个等级。基本思想是只使用已经存在的rank 列,但如果同一排名出现多次,则让时间戳打破排序规则。

【讨论】:

  • 谢谢蒂姆。但是,这实际上并不能解决我的问题。我正在修改问题以反映这种细微差别。
【解决方案2】:

如果您认为它的价值,您可以将其包装起来以发挥作用:

t=# with u as (
update r set rank = rank + 1 where rank >= 3
)
insert into r values('x',3,'New val!')
;
INSERT 0 1

结果:

t=# select * from r;
 id | rank |   text
----+------+----------
 x  |    0 |   Hello
 x  |    1 |   World
 x  |    2 |   Foo
 x  |    3 | New val!
 x  |    4 |   Bar
 x  |    5 |   Baz
(6 rows)

还值得一提的是,您可能在高负载系统上遇到并发“追逐条件”问题。上面的代码只是一个示例

【讨论】:

    【解决方案3】:

    您可以有一个“计算排名”,它是一个double precision 和一个“显示排名”,它是一个使用row_number 输出窗口函数计算的整数。

    当插入应该在两行之间排名的行时,将新排名计算为两个排名的算术平均值。

    优点是您不必更新现有行。
    不利的一面是,您必须先计算显示的排名,然后才能插入新行,以便知道在哪里插入。

    此解决方案(与所有其他解决方案一样)受制于竞争条件。
    要处理这些,您可以使用表锁或可序列化事务。

    【讨论】:

      【解决方案4】:

      防止竞争条件的唯一方法是锁定表

      https://www.postgresql.org/docs/current/sql-lock.html

      当然,如果有大量更新和插入,这会减慢您的速度。

      如果可以以某种方式限制更新的范围,那么您可以在该范围上执行 SELECT .... FOR UPDATE。例如,如果记录有 parent_id,您可以先在父记录上执行 select for update,任何其他执行相同 select for update 的插入都必须等到您的事务完成。

      https://www.postgresql.org/docs/current/explicit-locking.html#:~:text=5.-,Advisory%20Locks,application%20to%20use%20them%20correctly.

      阅读关于咨询锁的部分,看看您是否可以在您的应用程序中使用它们。它们不是由系统强制执行的,因此您需要小心编写应用程序的方式。

      【讨论】:

        猜你喜欢
        • 2010-09-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-09-22
        • 2020-05-03
        • 2020-09-26
        相关资源
        最近更新 更多