De enige manier om dit te doen is met een codecontroletabel ...
create table code_control
(year number(4,0) not null
, type varchar2(1) not null
, last_number number(38,0) default 1 not null
, primary key (year,type)
)
organization index
/
... die zo wordt onderhouden ...
create or replace function get_next_number
(p_year in number, p_type in varchar2)
return number
is
pragma autonomous_transaction;
cursor cur_cc is
select last_number + 1
from code_control cc
where cc.year= p_year
and cc.type = p_type
for update of last_number;
next_number number;
begin
open cur_cc;
fetch cur_cc into next_number;
if cur_cc%found then
update code_control
set last_number = next_number
where current of cur_cc;
else
insert into code_control (year,type)
values (p_year, p_type)
returning last_number into next_number;
end if;
commit;
return next_number;
end;
/
Het belangrijkste is de SELECT ... VOOR UPDATE. Pessimistische vergrendeling garandeert uniciteit in een omgeving met meerdere gebruikers. De PRAGMA zorgt ervoor dat het onderhouden van code_control
vervuilt de bredere transactie niet. Het stelt ons in staat om de functie in een trigger aan te roepen zonder deadlocks.
Hier is een tabel met een sleutel zoals die van jou:
create table t42
(year number(4,0) not null
, type varchar2(1) not null
, id number(38,0)
, primary key (year,type, id)
)
/
create or replace trigger t42_trg
before insert on t42 for each row
begin
:new.id := get_next_number(:new.year, :new.type);
end;
/
Ik heb niets meer in petto voordat ik t42
vul :
SQL> select * from code_control;
no rows selected
SQL> select * from t42;
no rows selected
SQL> insert into t42 (year, type) values (2016, 'A');
1 row created.
SQL> insert into t42 (year, type) values (2016, 'A');
1 row created.
SQL> insert into t42 (year, type) values (2016, 'A');
1 row created.
SQL> insert into t42 (year, type) values (2016, 'B');
1 row created.
SQL> insert into t42 (year, type) values (2016, 'A');
1 row created.
SQL> insert into t42 (year, type) values (2017, 'A');
1 row created.
SQL> select * from t42;
YEAR T ID
---------- - ----------
2016 A 1
2016 A 2
2016 A 3
2016 A 4
2016 B 1
2017 A 1
6 rows selected.
SQL> select * from code_control;
YEAR T LAST_NUMBER
---------- - -----------
2016 A 4
2016 B 1
2017 A 1
SQL>
Het voor de hand liggende bezwaar tegen deze implementatie is dus schaalbaarheid. Transacties invoegen worden geserialiseerd op de code_control
tafel. Dat is absoluut waar. Het slot wordt echter zo kort mogelijk vastgehouden, dus dit zou geen probleem moeten zijn, zelfs niet als de t42
tabel wordt vele malen per seconde gevuld.
Als de tafel echter wordt onderworpen aan enorme aantallen gelijktijdige inserties, kan de vergrendeling een probleem worden. Het is van cruciaal belang dat de tafel voldoende slots voor geïnteresseerde transacties heeft (INITRANS, MAXTRANS) om aan gelijktijdige eisen te kunnen voldoen. Maar erg drukke systemen hebben misschien een slimmere implementatie nodig (misschien genereren ze de ID's in batches); verlaat anders de samengestelde sleutel ten gunste van een sequentie (omdat sequenties schalen in omgevingen met meerdere gebruikers).