Reservable Columns und Journaltabelle

10.
Oktober
2024
Veröffentlicht von: Petr Novak

Mit Reservable Columns wird von Oracle ein neues Locking Handling eingeführt.

 

Reservable Column

Mit Reservable Columns wird ein neuer Locking Mechanismus eingeführt. Es soll damit das Handling von langen Transaktionen mit Updates auf numerischen Spalten erleichtert werden.
Typisches Beispiel: In einem Webshop befüllen viele Benutzer gleichzeitig ihren Warenkorb, die Änderungen am Restbestand sollten dabei nicht blockiert werden.

Als Reservable kann nur eine numerische Spalte deklariert oder geändert werden, NULL Werte sind nicht erlaubt. 
Eine Tabelle kann bis zu 10 Reservable Columns haben. Ein Check Constraint auf eine Reservable Column ist möglich.

create table T
(ID        number primary key,
 NAME      varchar2(32),
 ANZAHL    number,
 RC_ANZAHL number reservable);

alter table T add  constraint T_CONRC check (RC_ANZAHL>0);
alter table T add  RC_ANZAHL2 number reservable;


Journaltabelle

Im Hintergrund wird für die Tabelle mit Reservable Column im selben Tablespace eine Journaltabelle mit dem Namen SYS_RESERVJRNL_<OBJECT_ID der Originaltabelle> angelegt. 

Die neuangelegte Journaltabelle:

desc SYS_RESERVJRNL_703615
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 ORA_SAGA_ID$                                       RAW(16)
 ORA_TXN_ID$                                        RAW(8)
 ORA_STATUS$                                        CHAR(12)
 ORA_STMT_TYPE$                                     CHAR(16)
 ID                                        NOT NULL NUMBER
 RC_ANZAHL_OP                                       CHAR(7)
 RC_ANZAHL_RESERVED                                 NUMBER
 RC_ANZAHL2_OP                                      CHAR(7)
 RC_ANZAHL2_RESERVED                                NUMBER

insert into T values (1,'Orange',10,10,100);
insert into T values (2,'Banane',20,20,200);
commit;

alter table T modify RC_ANZAHL2 not reservable;


In der Journaltabelle werden dann entsprechende Spalten gelöscht:

desc SYS_RESERVJRNL_703615
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 ORA_SAGA_ID$                                       RAW(16)
 ORA_TXN_ID$                                        RAW(8)
 ORA_STATUS$                                        CHAR(12)
 ORA_STMT_TYPE$                                     CHAR(16)
 ID                                        NOT NULL NUMBER
 RC_ANZAHL_OP                                       CHAR(7)
 RC_ANZAHL_RESERVED                                 NUMBER

Wenn die Tabelle keine Reservable Columns mehr beinhaltet, wird die Journaltabelle gelöscht.


Eine Session sieht in der Journaltabelle nur eigene Änderungen, diese werden nicht aggregiert:

update T set RC_ANZAHL=RC_ANZAHL+1 where id=1;
update T set RC_ANZAHL=RC_ANZAHL+2 where id=1;
update T set RC_ANZAHL2=RC_ANZAHL2+100 where id=1;

select ORA_TXN_ID$,ORA_STATUS$,ORA_STMT_TYPE$,ID,RC_ANZAHL_OP,RC_ANZAHL_RESERVED, 
RC_ANZAHL2_OP,RC_ANZAHL2_RESERVED from SYS_RESERVJRNL_703615;

ORA_TXN_ID$      ORA_STATUS$  ORA_STMT_TYPE$  ID RC_ANZA RC_ANZAHL_RESERVED RC_ANZA RC_ANZAHL2_RESERVED
---------------- ------------ -------------- --- ------- ------------------ ------- -------------------
03000F00D5EB0000 ACTIVE       UPDATE           1 +                        1
03000F00D5EB0000 ACTIVE       UPDATE           1 +                        2
03000F00D5EB0000 ACTIVE       UPDATE           1                            +                       100


Ein Verschieben der Journaltabelle ist nicht möglich:

alter table SYS_RESERVJRNL_703615 move tablespace USERS
ORA-55727: DML, ALTER, RENAME, and CREATE UNIQUE INDEX operations are not allowed on the reservation journal table "PNO"."SYS_RESERVJRNL_703615".


Non unique Index ist zwar möglich, würde ich aber nicht empfehlen.

create index SYS_RESERVJRNL_703615_IDX1 on SYS_RESERVJRNL_703615(ID)


Restriktionen

Reservable Column kann nicht indiziert werden und ist nicht in IOTs, External und Temporary Tabellen erlaubt.

Beispiele der weiteren Restriktionen:

update T set RC_ANZAHL=2*RC_ANZAHL;
ORA-55746: Reservable column update statement only supports + or - operations on a reservable column.

 

update T set RC_ANZAHL=RC_ANZAHL+2 where name='Banane';
ORA-55732: Reservable column update should specify all the primary key columns  in  the WHERE clause.

 

update T set ANZAHL=10 ,RC_ANZAHL=10 , RC_ANZAHL2=100 where id=1;
ORA-55735: Reservable and non-reservable columns cannot be updated in the same statement.

 

drop table T;
ORA-55764: Cannot DROP or MOVE tables with reservable columns. 
First run "ALTER TABLE <table_name> MODIFY (<reservable_column_name> NOT RESERVABLE)" and then
DROP or MOVE the table.

 

create table T2 as select * from T;
ORA-55762: Reservable column property is not supported for columns of a view


Locking

Lock beim Update einer 'normalen' Spalte:

update t set RC_ANZAHL=RC_ANZAHL+2 where id=1;
1 row updated.

select sid,id1,id2,type,lmode,object_name,request from v$lock l 
left outer join dba_objects o on  o.object_id=l.id1 where  type!='AE' and sid in (52,53) order by sid,object_name ;

    Sid            ID1            ID2 TY          LMODE OBJECT_NAME                           REQUEST
------- -------------- -------------- -- -------------- ------------------------------ --------------
     52         703616              0 TM              3 SYS_RESERVJRNL_703615                       0
     52         703615              0 TM              3 T                                           0
     52         131099          60544 TX              6                                             0
     53         703616              0 TM              3 SYS_RESERVJRNL_703615                       0
     53         703615              0 TM              3 T                                           0
     53         196608          60541 TX              6                                             0

 

Deadlock passiert auch nicht.

Session 52:

update t set RC_ANZAHL=RC_ANZAHL+1 where id=1;
1 row updated.


Session 53:

update t set RC_ANZAHL=RC_ANZAHL+2 where id=2;
1 row updated.

update t set RC_ANZAHL=RC_ANZAHL+1 where id=1;
1 row updated.


Session 52:

update t set RC_ANZAHL=RC_ANZAHL+2 where id=2;
1 row updated.

 

select sid,id1,id2,type,lmode,object_name,request from v$lock l
left outer join dba_objects o on  o.object_id=l.id1 where  type!='AE' and sid in (52,53) order by sid,object_name ;


    Sid            ID1            ID2 TY          LMODE OBJECT_NAME                           REQUEST
------- -------------- -------------- -- -------------- ------------------------------ --------------
     52         703616              0 TM              3 SYS_RESERVJRNL_703615                       0
     52         703615              0 TM              3 T                                           0
     52         458774          45521 TX              6                                             0
     53         703616              0 TM              3 SYS_RESERVJRNL_703615                       0
     53         703615              0 TM              3 T                                           0
     53         327693          60557 TX              6                                             0
 


Update einer ‚normalen‘ Spalte und Update einer Reservable Column - sie blockieren sich nicht .

Session 52:

update t set RC_ANZAHL=RC_ANZAHL+1 where id=1;
1 row updated.


Session 53:

update t set ANZAHL=2*ANZAHL where id=1;
1 row updated.


Session 54:

update t set  RC_ANZAHL=RC_ANZAHL+1 where id=1;
1 row updated.

select sid,id1,id2,type,lmode,object_name,request from v$lock l
left outer join dba_objects o on  o.object_id=l.id1 where  type!='AE' and sid in (52,53,54) order by sid,object_name ;


   Sid            ID1            ID2 TY          LMODE OBJECT_NAME                           REQUEST
------ -------------- -------------- -- -------------- ------------------------------ --------------
    52         703616              0 TM              3 SYS_RESERVJRNL_703615                       0
    52         703615              0 TM              3 T                                           0
    52         131074          71412 TX              6                                             0
    53         703615              0 TM              3 T                                           0
    53         327706          71423 TX              6                                             0
    54         703616              0 TM              3 SYS_RESERVJRNL_703615                       0
    54         703615              0 TM              3 T                                           0
    54         393235          71475 TX              6                                             0


Sichtbarkeit

Da eine Session andere Änderungen in der Journaltabelle nicht sieht, kann es ein bisschen irreführend sein.
Beim Standard Update muss man die Zeile exklusiv für sich haben, es wird der aktuelle Wert in der Tabelle geprüft.
Beim Update einer Reservable Column wird intern auch der Inhalt der Journaltabelle geprüft. 
Check Constraint T_CONRC  mit (RC_ANZAHL>0).

Session 52:

update T set RC_ANZAHL=RC_ANZAHL-5 where id=1;


Session 53:

select RC_ANZAHL from T where id=1;

     RC_ANZAHL
--------------
            10

update T set RC_ANZAHL=RC_ANZAHL-8 where id=1;

ERROR at line 1:
ORA-02290: check constraint (PNO.T_CONRC) violated


Im SQLTrace sieht man interne Statements wie:

SELECT NVL(((select NVL(sum(RC_ANZAHL_RESERVED), 0)
from
 SYS_RESERVJRNL_703615 where ORA_STATUS$ = 'ACTIVE' and RC_ANZAHL_OP = '+'
  and  ORA_TXN_ID$ = :TXID and ID = :VAL1 ) - (select
  NVL(sum(RC_ANZAHL_RESERVED), 0) from SYS_RESERVJRNL_703615 where
  ORA_STATUS$ = 'ACTIVE' and RC_ANZAHL_OP = '-' and ID = :VAL1 )), 0) as
  curr_reserv from dual


Commit im SQLTrace

Im Unterschied zum Commit eines Update auf 'normalen' Spalten,  sieht man bei Reservable Columns im SQLTrace nach dem Commit zusätzliche interne Statements - eigentliches Update mit den Werten aus der Journaltabelle und Löschen der Daten aus der Journaltabelle:

commit;

update (select B$.ID,B$.RC_ANZAHL,ORA_ESCR_AGG$.RC_ANZAHL_RESVAL from PNO.T
  B$ inner join (select EJ$.ID,NVL(sum(case when RC_ANZAHL_OP = '+' then
  RC_ANZAHL_RESERVED when RC_ANZAHL_OP = '-' then -1*RC_ANZAHL_RESERVED else
  0 end), 0) as RC_ANZAHL_RESVAL  from PNO.SYS_RESERVJRNL_703615 EJ$
where
 ORA_TXN_ID$=:1 and ORA_STATUS$='ACTIVE' and ORA_SAGA_ID$ IS NULL group by
  EJ$.ID order by EJ$.ID desc) ORA_ESCR_AGG$ on B$.ID=ORA_ESCR_AGG$.ID)
  ORA_ESCR_JOIN$ set ORA_ESCR_JOIN$.RC_ANZAHL = ORA_ESCR_JOIN$.RC_ANZAHL +
  ORA_ESCR_JOIN$.RC_ANZAHL_RESVAL


delete from PNO.SYS_RESERVJRNL_703615
where
 ORA_TXN_ID$ = :1 and ORA_SAGA_ID$ IS NULL


Fazit

Mit dem Einführen von Reservable Columns stellt Oracle einen neuen interessanten  Mechanismus zum Reduzieren der Lock Contention in speziellen Situationen, zur Verfügung. Wie gefällt es Ihnen?

 

 

 

Jede Menge Know-how für Sie!

In unserer Know-How Datenbank finden Sie mehr als 300 ausführliche Beiträge zu den Oracle-Themen wie DBA, SQL, PL/SQL, APEX und vielem mehr.
Hier erhalten Sie Antworten auf Ihre Fragen.