如何管理表行中的连续列值

时间:2009-08-20 07:19:18

标签: java database oracle concurrency

关于我想做的事情的一点介绍:

考虑这样一种情况,即公司的不同人员每年一次获得到某地的所有费用支付旅行。可能有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,例如。

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

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

7 个答案:

答案 0 :(得分:7)

为什么要让它变得复杂?

只需在输入时插入所有预订,然后插入重新分配点的时间戳。

然后在你的查询中只使用时间戳对它们进行排序。

如果有人在相同的毫秒内保留一个点,那么只需使用随机方法来分配顺序。

答案 1 :(得分:1)

为什么需要显式存储索引?相反,您可以存储每个人的订单(从不更改)以及活动标记。在你的例子中,如果第16个人退出,你只需将它们标记为无效。

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

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

这种方法消除了对数据库进行批量更新的需要(您只需更新一行),因此大多可以缓解您的事务完整性问题。

答案 2 :(得分:1)

另一种方法是在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上执行查询。

答案 3 :(得分:0)

  1. 将人员添加到表格时,请按照ID按照添加顺序升序的方式为其提供ID。这可以是时间戳。

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

  3. 从INDEX_NR

  4. 的INDEX _ NR&lt; = 16的表中选择*

    步骤#2似乎很复杂,但实际上非常简单:

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

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

答案 4 :(得分:0)

在运行时计算索引:

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

答案 5 :(得分:0)

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

在旅行表中插入记录时,获取索引的方式是

SELECT MAX(INDEX_NR)+ 1 AS NEXT_INDEX_NR FROM TABLE

并使用它作为新索引(这是完成Java端,然后是一个新的查询来插入记录)。很明显,为什么我们有多个点或保留具有相同的索引。“

呀。 Oracle的MVCC(“快照隔离”)错误地被不应该从事过IT的人使用。

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

你说你关心的是“回归错误”。我说“为什么你需要关注一个 DEMONSTRABLY 超出治疗的应用程序中的”回归错误“?因为你的老板为这些垃圾付了很多钱,而且你不想成为那个因为传递信息而被枪杀的钢琴家?

答案 6 :(得分:0)

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

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

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