"实体"特定序列

use*_*735 8 sql oracle

背景

我有很多不同的"东西"(特定领域的项目/实体/主题),对于"东西"所有者(人类)是可见的.业主将用数字识别他们的"事物".而是显示一个大的"随机"数字,我想向他们展示一个对人类来说更容易的小数字(最好是从1开始的序列).业主们很自在地谈论"我的foo 37"和"她的酒吧128"."序列"可能有间隙,但附加的数字必须在"事物"的生命周期内保持不变.所以我需要一种方法来生成"thing"+所有者特定的id(当前称为"visible id").

"东西"+所有者组合的数量是10k +的规模.目前新的"东西"不能动态生成,但业主可以.

每个所有者的一个"事物"实例的数量相对较小,每个所有者约为几十个,但是没有可以从业务规则中获得的硬性上限.经常创建和删除新的"事物"实例.

考虑的选择

我在一个SO问题Oracle Partitioned Sequence中找到了一个很好的讨论,它解决了我所遇到的几乎相同的问题.

到目前为止,我已经考虑了以下选项:

  1. 我认为一个标准的数据库序列将是完美的,但这需要我动态创建大量的"东西"+所有者特定序列,并在插入过程中解析序列名称.(并在主人离开时丢弃序列.)我不确定是否创建大量序列是一个很好的做法(对我来说10k +数据库对象是一个巨大的数字).
  2. 我也认为臭名昭着,max(visible_id) + 1但我们会遇到正常的并发问题,所以这是不行的.
  3. 不要店主特定ID保存到在所有的数据库,而是生成它在选择类似建议亚当MUSCH.这是一个很棒的主意,但不幸的是,在"事物"实例生命周期中,id必须是相同的.
  4. 让业主命名为"东西",避免整个问题.但他们根本不喜欢这个想法 - "为什么我要打扰,说foo 16真是太容易了." !

是否有其他方法可以解决此问题,还是应该开始动态创建序列?如果序列是答案,请详细说明可能存在的陷阱(如DDL中的隐式提交).

我对Oracle 11gR2和12c解决方案都感兴趣(如果它们不同的话).

伪代码来说明问题

create table foo (
 id number primary key -- the key for computers
,owner_id number
,visible_id number -- the key for humans
,data_ varchar2(20)
);

create constraint foo_u1 unique foo(owner_id, visible_id);

-- primary key sequence
create sequence foo_id_seq;

insert into foo values(
 foo_id_seq.nextval
,1
,1 -- what to put here ?
,'lorem ipsum'
);

insert into foo values(
 foo_id_seq.nextval
,2
,1 -- what to put here ?
,'dolor sit amet'
);

select visible_id, data_ from foo where owner = 2 order by visible_id;
Run Code Online (Sandbox Code Playgroud)

Mat*_*eak 2

由于间隙是可以接受的,因此您应该实施“选项 2”的变体。允许间隙意味着您的同步可以快速完成:竞争会话只需检查并继续,而不必等待其他会话是否提交或回滚。

如果 Oracle 提供了一个INSERT INTO..NOWAIT选项,这将会很容易。就目前情况而言,我可能会参与其中DBMS_LOCK。以下是我对 API 的看法。

它对您拥有的最大可见 ID 做出了一些假设,因为您在原始帖子中做出了这些假设。

CREATE OR REPLACE PACKAGE foo_api AS
  PROCEDURE create_foo (p_owner_id NUMBER, p_data VARCHAR2);
END foo_api;

CREATE OR REPLACE PACKAGE BODY foo_api AS
  -- We need to call allocate_unique in an autonomous transaction because
  -- it commits and the calling program may not want to commit at this time
  FUNCTION get_lock_handle (p_owner_id NUMBER, p_visible_id NUMBER)
    RETURN VARCHAR2 IS
    PRAGMA AUTONOMOUS_TRANSACTION;
    l_lock_handle   VARCHAR2 (128);
  BEGIN
    DBMS_LOCK.allocate_unique (
      lockname  => 'INSERT_FOO_' || p_owner_id || '_' || p_visible_id,
      lockhandle => l_lock_handle
    );
    COMMIT;
    RETURN l_lock_handle;
  END;


  PROCEDURE create_foo (p_owner_id NUMBER, p_data VARCHAR2) IS
    -- This is the highest visible ID you'd ever want.
    c_max_visible_id   NUMBER := 1000;
  BEGIN
   <<id_loop>>
    FOR r_available_ids IN (SELECT a.visible_id
                            FROM   (SELECT ROWNUM visible_id
                                    FROM   DUAL
                                    CONNECT BY ROWNUM <= c_max_visible_id) a
                                   LEFT JOIN foo
                                     ON foo.owner_id = p_owner_id
                                     AND foo.visible_id = a.visible_id
                            WHERE  foo.visible_id IS NULL) LOOP
      -- We found a gap
      -- We could try to insert into it.  If another session has already done so and
      -- committed, we'll get an ORA-00001.  If another session has already done so but not 
      -- yet committed, we'll wait.  And waiting is bad.
      -- We'd like an INSERT...NO WAIT, but Oracle doesn't provide that.
      -- Since this is the official API for creating foos and we have good application 
      -- design to ensure that foos are not created outside this API, we'll manage 
      -- the concurrency ourselves.
      --
      -- Try to acquire a user lock on the key we're going to try an insert.
      DECLARE
        l_lock_handle       VARCHAR2 (128);
        l_lock_result       NUMBER;
        l_seconds_to_wait   NUMBER := 21600;
      BEGIN
        l_lock_handle := get_lock_handle (
          p_owner_id => p_owner_id,
          p_visible_id => r_available_ids.visible_id
        );

        l_lock_result := DBMS_LOCK.request (lockhandle => l_lock_handle,
                                            lockmode   => DBMS_LOCK.x_mode,
                                            timeout    => 0, -- Do not wait
                                            release_on_commit => TRUE);

        IF l_lock_result = 1 THEN
          -- 1 => Timeout -- this could happen.
          -- In this case, we want to move onto the next available ID.
          CONTINUE id_loop;
        END IF;

        IF l_lock_result = 2 THEN
          -- 2 => Deadlock (this should never happen, but scream if it does).
          raise_application_error (
            -20001,
               'A deadlock occurred while trying to acquire Foo creation lock for '
            || p_owner_id
            || '_'
            || r_available_ids.visible_id
            || '.  This is a programming error.');
        END IF;

        IF l_lock_result = 3 THEN
          -- 3 => Parameter error (this should never happen, but scream if it does).
          raise_application_error (
            -20001,
               'A parameter error occurred while trying to acquire Foo creation lock for '
            || p_owner_id
            || '_'
            || r_available_ids.visible_id
            || '.  This is a programming error.');
        END IF;

        IF l_lock_result = 4 THEN
          -- 4 => Already own lock (this should never happen, but scream if it does).
          raise_application_error (
            -20001,
               'Attempted to create a Foo creation lock and found lock already held by session for '
            || p_owner_id
            || '_'
            || r_available_ids.visible_id
            || '.  This is a programming error.');
        END IF;

        IF l_lock_result = 5 THEN
          -- 5 => Illegal lock handle (this should never happen, but scream if it does).
          raise_application_error (
            -20001,
               'An illegal lock handle error occurred while trying to acquire Foo creation lock for '
            || p_owner_id
            || '_'
            || r_available_ids.visible_id
            || '.  This is a programming error.');
        END IF;
      END;

      -- If we get here, we have an exclusive lock on the owner_id / visible_id 
      -- combination.  Attempt the insert
      BEGIN
        INSERT INTO foo (id,
                         owner_id,
                         visible_id,
                         data_)
        VALUES (foo_id_seq.NEXTVAL,
                p_owner_id,
                r_available_ids.visible_id,
                p_data);

        -- If we get here, we are done.
        EXIT id_loop;
      EXCEPTION
        WHEN DUP_VAL_ON_INDEX THEN
          -- Unfortunately, if this happened, we would have waited until the competing 
          -- session committed or rolled back.  But the only way it
          -- could have happened if the competing session did not use our API to create 
          -- or update the foo.
          -- TODO: Do something to log or alert a programmer that this has happened, 
          -- but don't fail.
          CONTINUE id_loop;
      END;
    END LOOP;
  END create_foo;
END foo_api;
Run Code Online (Sandbox Code Playgroud)