一个"实体"具体序列

时间:2016-08-12 08:22:53

标签: sql oracle

背景

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

""" +所有者组合的规模为10k +。目前新的"东西"不能动态生成,但所有者可以。

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

考虑选项

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

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

  1. 我认为一个标准的数据库序列会非常好,但这需要我动态创建大量的"事情" +所有者特定序列,并在插入期间解析序列名称。 (并在主人离开时删除序列。)我不确定创建大量序列是否是一个好习惯(对我来说,10k +数据库对象是一个巨大的数字)。
  2. 我也认为臭名昭着max(visible_id) + 1,但我们会遇到正常的并发问题,所以这是不行的。
  3. 根本不要将所有者特定ID保存到数据库中,而是通过suggestedAdam Musch之类的选择中生成它。这是一个很棒的主意,但不幸的是,在这个过程中,id必须是相同的。实例生命周期。
  4. 让所有者命名"事情"避免整个问题。但是他们根本不喜欢这个主意 - "为什么我要打扰,只是说foo 16很容易。"
  5. 问题

    是否有其他方法可以解决此问题,还是应该开始动态创建序列?如果序列是答案,请详细说明可能存在的陷阱(如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;
    

1 个答案:

答案 0 :(得分:2)

由于差距正常,您应该实施"选项2"的变体。允许间隙意味着可以快速完成同步:竞争会话只需检查并继续,而不必等待其他人提交或回滚。

如果Oracle提供DBMS_LOCK选项,这将很容易。事实上,我可能会涉及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; 。以下是我对API的看法。

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

 <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <executions>
                        <execution>
                            <id>default-test</id>
                            <goals>
                                <goal>test</goal>
                            </goals>
                            <configuration>
                                <argLine>-Xmx2048m -XX:MaxPermSize=512m</argLine>
                                <properties>
                                    <property>
                                        <name>listener</name>
                                        <value>org.uncommons.reportng.HTMLReporter, org.uncommons.reportng.JUnitXMLReporter</value>
                                    </property>
                                </properties>
                                <systemPropertyVariables>
                                    <org.uncommons.reportng.title>OBS Test Suite</org.uncommons.reportng.title>
                                    <org.uncommons.reportng.escape-output>false</org.uncommons.reportng.escape-output>
                                </systemPropertyVariables>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
            </plugins>