Sql ";“实体”;特定序列
背景 我有很多不同的“事物”(特定于领域的项目/实体/主题),它们对“事物”所有者(人类)是可见的。业主们将用数字来识别他们的“东西”。而不是显示一个大的“随机”数字,我想给他们显示一个小的数字(最好是从1开始的序列),这对人类来说更容易。老板们很乐意谈论“我的富37”和“她的酒吧128”。“序列”可以有间隙,但附加的编号必须在“对象”的生命周期内保持不变。所以我需要一种方法来生成“thing”+特定于所有者的id(目前称为“visible id”) “物”+所有者组合的数量为10k+。目前,新的“东西”无法动态生成,但所有者可以 每个所有者拥有一个“thing”实例的数量相对较少,大约为数十个,但是没有可以从业务规则中派生的硬上限。新的“东西”实例经常被创建和删除 考虑过的选项 我发现一个非常好的讨论是在一个非常好的问题中进行的,它解决了与我几乎相同的问题 到目前为止,我已经考虑了以下选择:Sql ";“实体”;特定序列,sql,oracle,Sql,Oracle,背景 我有很多不同的“事物”(特定于领域的项目/实体/主题),它们对“事物”所有者(人类)是可见的。业主们将用数字来识别他们的“东西”。而不是显示一个大的“随机”数字,我想给他们显示一个小的数字(最好是从1开始的序列),这对人类来说更容易。老板们很乐意谈论“我的富37”和“她的酒吧128”。“序列”可以有间隙,但附加的编号必须在“对象”的生命周期内保持不变。所以我需要一种方法来生成“thing”+特定于所有者的id(目前称为“visible id”) “物”+所有者组合的数量为10k+。目前,新
max(visible_id)+1
,但是我们会遇到正常的并发问题,所以这是不可能的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;
是否有其他方法来解决此问题,或者我应该开始动态创建序列?如果序列是答案,请详细说明可能存在哪些陷阱(如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”的变体。允许间隙意味着您可以快速完成同步:竞争会话只需检查并继续,而不必等待其他会话是否提交或回滚 如果Oracle提供了一个
插入到..NOWAIT
选项,这将很容易。事实上,我可能会涉及到DBMS\u 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;
创建或替换包foo_api作为
程序创建_foo(p_所有者_id号,p_数据VARCHAR2);
结束福尤api;
创建或替换包体foo_api作为
--我们需要在自治事务中调用allocate_unique,因为
--它提交,调用程序此时可能不想提交
函数get\u lock\u handle(p\u所有者\u id号,p\u可见\u id号)
返回VARCHAR2是
布拉格自治交易;
l_锁柄VARCHAR2(128);
开始
DBMS_LOCK.allocate_unique(
lockname=>“插入|FOO|p|u所有者|u id|p|u可见|u id”,
锁定手柄=>l\U锁定手柄
);
犯罪
返回锁定手柄;
结束;
创建\u foo(p\u所有者\u id号,p\u数据VARCHAR2)的过程是
--这是你想要的最高可见ID。
c_max_visible_id NUMBER:=1000;
开始
对于中的r\u可用\u id(选择一个可见\u id
从(选择ROWNUM visible\u id
来自双重
通过ROWNUM p_owner_id连接,
p_visible_id=>r_available_id.visible_id
);
l\u lock\u result:=DBMS\u lock.request(lockhandle=>l\u lock\u handle,
lockmode=>DBMS_LOCK.x_模式,
超时=>0,--不要等待
发布时发布(提交=>TRUE);
如果l_lock_result=1,则
--1=>超时--这可能发生。
--在本例中,我们希望转到下一个可用ID。
继续id_循环;
如果结束;
如果l_lock_result=2,则
--2=>死锁(这种情况永远不会发生,但如果发生就尖叫)。
引发应用程序错误(
-20001,
'尝试获取的Foo创建锁时发生死锁'
||p_所有者\u id
|| '_'
||r\u可用\u id。可见\u id
||“。这是一个编程错误。”);
如果结束;
如果l_lock_result=3,则
--3=>参数错误(这种情况永远不会发生,但如果发生会尖叫)。
引发应用程序错误(
-20001,
'尝试获取的Foo创建锁时发生参数错误'
||p_所有者\u id
|| '_'
||r\u可用\u id。可见\u id
||“。这是一个编程错误。”);
如果结束;
如果l_lock_result=4,则
--4=>已经拥有锁(这种情况永远不会发生,但如果发生会尖叫)。
引发应用程序错误(
-20001,
'尝试创建Foo创建锁,但发现会话已为持有该锁'
||p_所有者\u id
|| '_'
||r\u可用\u ID可见