【问题标题】:How to manage consecutive column values in table rows如何管理表格行中的连续列值
【发布时间】:2009-08-20 07:19:17
【问题描述】:

我想做的小演示:

考虑这样一种情况,公司的不同人员每年一次去某个地方进行一次所有费用支付的旅行。可能有 1000 人有资格参加这次旅行,但只有 16 个名额可用。

这 16 个地点中的每一个都有一个关联的索引,该索引必须从 1 到 16。预订中的索引从 17 开始。

前 16 名申请的人将在行程中获得一个确定的位置。其余的最终在预订名单上。如果前 16 人中的一个人取消了,第一个有预订的人将获得他的位置,所有索引都会重新编号以补偿取消的人。

所有这些都在带有 Oracle DB 的 Java Web 应用程序中进行管理。

现在,我的问题:

我必须以正确的方式管理索引(所有顺序,没有重复的索引),可能有数百人同时申请旅行。

在表中插入记录为行程时,获取索引的方式是通过

SELECT MAX(INDEX_NR) + 1 AS NEXT_INDEX_NR FROM TABLE

并将其用作新索引(这是在 Java 端完成的,然后是新查询以插入记录)。很明显为什么我们有多个具有相同索引的景点或预订。因此,假设有 19 人在旅途中,因为其中 4 人的索引为 10。

我该如何管理?到目前为止,我一直在考虑 3 种方法:

  1. 对 DB 事务使用隔离级别 Serializable(不喜欢这个);
  2. 插入没有INDEX_NR 的记录,然后让触发器管理这些事情……以某种方式(以前从未使用过触发器);
  3. 每条记录还有一个UPDATED 列。我可以以某种方式使用它吗? (请注意,我不能丢失 INDEX_NR,因为应用程序的其他部分会使用它)。

有没有最好的方法来做到这一点?

【问题讨论】:

    标签: java database oracle concurrency


    【解决方案1】:

    为什么要复杂?

    只需在输入时插入所有预订,并插入他们预订位置的时间戳。

    然后在您查询时,只需使用时间戳对它们进行排序。

    当然,也有可能有人在同一毫秒内预订了一个位置,然后只是使用随机方法来分配顺序。

    【讨论】:

    • 不想让它变得复杂。我自己喜欢 KISS (en.wikipedia.org/wiki/KISS_principle) 解决方案,但代码库已经存在。我也在寻找替代方案,但也在寻找上述方法。通过这种方式,我可以以较小的影响(回归错误的可能性较小)修复无法正常工作的行为,然后修改应用程序的其他位置以适应新行为。
    • 如果您必须避免更改,可以选择在 index_nr 列上设置唯一约束,这样数据库本身将避免重复,缺点是您必须捕获 sqlexception它会抛出并确保您使用新的更高的 Index_nr 重试
    • 您可以使用序列而不是时间戳。如果在 RAC 上,您将需要 ORDER 子句来确保在每个节点上分配的序列号是按顺序排列的。哦,解雇 Java 开发人员,因为他们显然不知道“并发”和“原子性”的概念。
    【解决方案2】:

    为什么需要显式存储索引?相反,您可以将每个人的订单(从不更改)与活动标志一起存储。在您的示例中,如果第 16 个人退出,您只需将其标记为非活动状态。

    要计算一个人是否有资格参加旅行,您只需计算订单少于该人的活跃人数:

    select count(*)
    from CompetitionEntry
    where PersonOrder < 16
      and Active = 1
    

    这种方法无需对数据库进行批量更新(您只需要更新一行),因此在很大程度上缓解了您的事务完整性问题。

    【讨论】:

      【解决方案3】:

      另一种方法是在 select 上显式锁定另一个表上的记录。

      -- Initial Setup
      CREATE TABLE NUMBER_SOURCE (ID NUMBER(4));
      
      INSERT INTO NUMBER_SOURCE(ID) VALUES 0;
      
      -- Your regular code
      SELECT ID AS NEXT_INDEX_NR FROM NUMBER_SOURCE FOR UPDATE; -- lock!
      
      UPDATE NUMBER_SOURCE SET ID = ID + 1;
      
      INSERT INTO TABLE ....
      
      COMMIT; -- releases lock!
      

      在提交(或回滚)之前,没有其他事务能够对表 NUMBER_SOURCE 执行查询。

      【讨论】:

        【解决方案4】:
        1. 向表中添加人员时,为他们提供一个 ID,使 ID 按照添加顺序升序排列。这可以是时间戳。

        2. 从表中选择所有符合条件的记录,按 ID 排序,并更新它们的 INDEX_NR

        3. Select * from table where INDEX_NR

        第 2 步看起来很复杂,但实际上很简单:

            update (
                select *
                from TABLE
                where ...
                order by ID
            )
            set INDEX_NR = INDEXSEQ.NEXTVAL
        

        不要忘记将序列重置为 1。

        【讨论】:

          【解决方案5】:

          在运行时计算您的索引:

          CREATE OR REPLACE VIEW v_person
          AS
          SELECT  id, name, ROW_NUMBER() OVER (ORDER BY id) AS index_rn
          FROM    t_person
          
          CREATE OR REPLACE TRIGGER trg_person_ii
          INSTEAD OF INSERT ON v_person
          BEGIN
                  INSERT
                  INTO    t_person (id, name)
                  VALUES  (:new.id, :new.name);
          END;
          
          CREATE OR REPLACE TRIGGER trg_person_iu
          INSTEAD OF UPDATE ON v_person
          BEGIN
                  UPDATE  t_person
                  SET     id = :new.id,
                          name = :new.name
                  WHERE   id = :old.id;
          END;
          
          CREATE OR REPLACE TRIGGER trg_person_id
          INSTEAD OF DELETE ON v_person
          BEGIN
                  DELETE
                  FROM    t_person
                  WHERE   id = :old.id;
          END;
          
          INSERT
          INTO    v_person
          VALUES  (1, 'test', 1)
          
          SELECT  *
          FROM    v_person
          
          --
          id   name   index_rn
          
           1   test          1
          
          INSERT
          INTO    v_person
          VALUES  (2, 'test 2', 1)
          
          SELECT  *
          FROM    v_person
          
          --
          id   name   index_rn
          
           1   test          1
           2   test 2        2
          
          
          DELETE
          FROM    v_person
          WHERE   id = 1
          
          SELECT  *
          FROM    v_person
          
          --
          id   name   index_rn
          
           2   test 2        1
          

          【讨论】:

            【解决方案6】:

            “我必须以正确的方式管理索引(所有顺序,无重复索引),可能有数百人同时申请旅行。

            在表中插入记录为行程时,获取索引的方式是通过

            从表中选择 MAX(INDEX_NR) + 1 作为 NEXT_INDEX_NR

            并将其用作新索引(这是在 Java 端完成的,然后是新查询以插入记录)。很明显为什么我们有多个具有相同索引的景点或预订。”

            是的。 Oracle 的 MVCC(“快照隔离”)被原本不应该从事 IT 工作的人错误地使用。

            真的,彼得是对的。您的索引号是,或者更确切地说应该是,他提到的有序时间戳上的一种“排名号”(这要求 DBMS 可以保证任何时间戳值在整个数据库中只出现一次)。

            您说您担心“回归错误”。我说“为什么您需要关注应用程序中的“回归错误”,该应用程序DEMONSTRABLY 无法治愈?”。因为你的老板为他们收到的废话付出了很多钱,而你不想成为因为传达信息而被枪杀的钢琴家?

            【讨论】:

            • 事情不是这样的。没有什么比重写这件事更能让我高兴了,但我是一名程序员……你知道……食物链中的最后一个。所以,归根结底,这不是我的决定,也不是我的决定。
            【解决方案7】:

            解决方案取决于您可以控制的内容。我假设您可以更改数据库和 Java 代码,但不要修改数据库方案,否则您必须调整太多 Java 代码。

            一个廉价的解决方案可能是在对 (trip_id, index_nr) 上添加唯一性约束,如果只有一次行程,则在 index_nr 上添加唯一性约束。另外添加一个检查约束 check(index_nr > 0) - 除非 index_nr 已经无符号。其他一切都在 Java 中完成:当插入您所描述的新申请人时,您必须添加代码来捕获同时插入其他人时的异常。如果某些记录被更新或删除,您要么必须忍受序列号之间的漏洞(通过选择具有最低 index_nr 的 16 个候选者,如 Quassnoi 在他的视图中所示),要么在之后手动填充它们(类似于 Aaron 建议的)每次更新/删除。

            如果 index_nr 在应用程序中主要用作只读,则更好的解决方案可能是结合 Peter 和 Quassnoi 的答案:使用时间戳(通过将当前时间定义为默认值由数据库自动插入)或一个自动递增的整数(默认由数据库插入)作为存储在表中的值。并使用视图(如 Quassnoi 定义的视图)访问表和 Java 自动计算的 index_nr。但也要定义这两个约束,比如廉价的解决方案。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2015-10-27
              • 2019-08-27
              • 2020-11-10
              • 2019-12-01
              • 2014-02-24
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多