sql - "entity"特定序列

标签 sql oracle

背景

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

“东西”+所有者组合的数量达到10k+的规模。目前新的“事物”无法动态生成,但所有者可以。

每个所有者的一个“事物”实例的数量相对较小,大约每个所有者数十个,但没有可以从业务规则中得出的硬性上限。新的“事物”实例经常被创建和删除。

考虑的选项

我在一个SO问题中发现了很好的讨论Oracle Partitioned Sequence这解决了我所遇到的几乎相同的问题。

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

  1. 我认为标准数据库序列非常好,但这需要我动态创建大量“事物”+所有者特定序列,并在插入期间解析序列名称。 (当所有者离开时,删除序列。)我不确定创建大量序列是否是一个好的做法(对我来说,10k+ 数据库对象是一个巨大的数字)。
  2. 我也被认为是臭名昭著max(visible_id) + 1但我们会遇到正常的并发问题,所以这是不行的。
  3. 根本不要将所有者特定 ID 保存到数据库中,而是在选择中生成它,如 suggested通过 Adam Musch 。这是一个很棒的主意,但不幸的是,id 在“thing”实例生命周期内必须保持相同。
  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;

最佳答案

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

如果 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;

关于sql - "entity"特定序列,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38913246/

相关文章:

django 多数据库路由不适用于多个模式

windows - PL/SQL Developer 不显示 "ő"字符

sql - T-Sql ISNUMERIC ('45D-1' ) 返回 1

mysql - 如何从 SQL 中的两列中获取唯一对

sql - 这个 SQL 查询怎么可能将重复值插入到数据库中?

java - 生成的 PDF 报告中缺少几个土耳其字母

sql - 如何查看oracle数据库中一个表的所有列的元数据?

sql - 如果引用 ID 为 NULL,则递归获取父 ID

sql - PostgreSQL 中涉及变量名的字符串比较查询的正确语法是什么?

sql-server - 向表中添加 BLOB 字段的性能开销