DBMS_XA
permet plusieurs choses parmi lesquelles coordonner plusieurs branches d’une transaction globale dans des sessions différentes. Les exemples n’étant pas foison, ni sur le web, ni sur les sites d’Oracle pour décrire l’utilisation d’une transaction multi-sessions, cet article présente un cas simple qui utilise une seule et même instance…
Oracle permet alors d’accédez aux modifications non encore validées des autres sessions ; c’est parce que le branches de la transaction globale sont « tightly coupled ».
Malheureusement, au moins en 11.2, ce fonctionnement se fait au prix d’une sérialisation des opérations des différentes sessions. Mauvais trip si vous pensiez vous en servir de cette fonctionnalité pour paralléliser vos traitements PL/SQL. Il faudra sans doute vous retourner vers des transactions globales déclenchées via les OCI ou une datasource native XA JDBC. Ce point est illustré à l’aide de 2 exemples ci-dessous. Mais assez parlez : à vos sessions SQL*Plus !
3 sessions coordonnées
Voici l’exemple de mise en oeuvre de 3 sessions qui utilisent des branches distinctes de la même transaction globale. En théorie au moins, on voit que les branches d’exécutent en parallèle et il n’y a pas à se décrocher (via DBMS_XA.XA_END
) de la transaction globale pour passer d’une session à l’autre :
Session 1 |
connect demo/demo
var rc number exec :rc:=dbms_xa.xa_settimeout(1200)
print rc
RC ---- 0
|
Session 2 |
connect demo/demo
var rc number exec :rc:=dbms_xa.xa_settimeout(1200)
print rc
RC ---- 0
|
Session 3 |
connect demo/demo
var rc number exec :rc:=dbms_xa.xa_settimeout(1200)
print rc
RC ---- 0
|
Session 1 |
set serveroutput on declare tx dbms_xa_xid:=dbms_xa_xid(1); begin :rc:=dbms_xa.xa_start( tx, dbms_xa.TMNOFLAGS); end; /
print rc
RC ---- 0
insert into x values(1);
col id format 999 select * from x;
ID ---- 1
col globalid format a8 col branchid format a32 col prep format 999 col state format a10 set lines 120 select GLOBALID, BRANCHID, PREPARECOUNT PREP, STATE, COUPLING from v$global_transaction;
GLOBALID BRANCHID PREP STATE -------- -------------------------------- ---- ------ 00000001 00000000000000000000000000000001 0 ACTIVE
|
Session 2 |
declare tx dbms_xa_xid; begin tx:=dbms_xa_xid( hextoraw('00000001'), hextoraw('00000000000000000000000000000002')); :rc:=dbms_xa.xa_start( tx, dbms_xa.TMNOFLAGS); end; /
print rc
RC --- 0
insert into x values (2);
col id format 999 select * from x;
ID ---- 1 2
col globalid format a8 col branchid format a32 col prep format 999 col state format a10 set lines 120 select GLOBALID, BRANCHID, PREPARECOUNT PREP, STATE, COUPLING from v$global_transaction;
GLOBALID BRANCHID PREP STATE -------- -------------------------------- ---- ---------- 00000001 00000000000000000000000000000001 0 ACTIVE 00000001 00000000000000000000000000000002 0 ACTIVE
|
Session 3 |
declare tx dbms_xa_xid; begin tx:=dbms_xa_xid( hextoraw('00000001'), hextoraw('00000000000000000000000000000003')); :rc:=dbms_xa.xa_start( tx, dbms_xa.TMNOFLAGS); end; /
print rc
RC --- 0
insert into x values (3);
col id format 999 select * from x;
ID ---- 3 1 2
col globalid format a8 col branchid format a32 col prep format 999 col state format a10 set lines 120 select GLOBALID, BRANCHID, PREPARECOUNT PREP, STATE, COUPLING from v$global_transaction;
GLOBALID BRANCHID PREP STATE -------- -------------------------------- ---- ---------- 00000001 00000000000000000000000000000001 0 ACTIVE 00000001 00000000000000000000000000000002 0 ACTIVE 00000001 00000000000000000000000000000003 0 ACTIVE
|
Session 1 |
declare tx dbms_xa_xid; begin tx:=dbms_xa_xid( hextoraw('00000001'), hextoraw('00000000000000000000000000000001')); :rc:=dbms_xa.xa_end( tx, dbms_xa.TMSUCCESS); end; /
print rc
RC --- 0
col id format 999 select * from x;
no rows selected
col globalid format a8 col branchid format a32 col prep format 999 col state format a10 set lines 120 select GLOBALID, BRANCHID, PREPARECOUNT PREP, STATE, COUPLING from v$global_transaction;
GLOBALID BRANCHID PREP STATE -------- -------------------------------- ---- ---------- 00000001 00000000000000000000000000000001 0 ACTIVE 00000001 00000000000000000000000000000002 0 ACTIVE 00000001 00000000000000000000000000000003 0 ACTIVE
|
Session 2 |
declare tx dbms_xa_xid; begin tx:=dbms_xa_xid( hextoraw('00000001'), hextoraw('00000000000000000000000000000002')); :rc:=dbms_xa.xa_end( tx, dbms_xa.TMSUCCESS); end; /
print rc
RC --- 0
col id format 999 select * from x;
no rows selected
col globalid format a8 col branchid format a32 col prep format 999 col state format a10 set lines 120 select GLOBALID, BRANCHID, PREPARECOUNT PREP, STATE, COUPLING from v$global_transaction;
GLOBALID BRANCHID PREP STATE -------- -------------------------------- ---- ---------- 00000001 00000000000000000000000000000001 0 ACTIVE 00000001 00000000000000000000000000000002 0 ACTIVE 00000001 00000000000000000000000000000003 0 ACTIVE
|
Session 3 |
declare tx dbms_xa_xid; begin tx:=dbms_xa_xid( hextoraw('00000001'), hextoraw('00000000000000000000000000000003')); :rc:=dbms_xa.xa_end( tx, dbms_xa.TMSUCCESS); end; /
print rc
RC --- 0
col id format 999 select * from x;
no rows selected
col globalid format a8 col branchid format a32 col prep format 999 col state format a10 set lines 120 select GLOBALID, BRANCHID, PREPARECOUNT PREP, STATE, COUPLING from v$global_transaction;
GLOBALID BRANCHID PREP STATE -------- -------------------------------- ---- ---------- 00000001 00000000000000000000000000000001 0 ACTIVE 00000001 00000000000000000000000000000002 0 ACTIVE 00000001 00000000000000000000000000000003 0 ACTIVE
|
Session 1 |
declare tx dbms_xa_xid; begin tx:=dbms_xa_xid( hextoraw('00000001'), hextoraw('00000000000000000000000000000001')); :rc:=dbms_xa.xa_prepare( tx); end; /
print rc
RC ---- 3
col globalid format a8 col branchid format a32 col prep format 999 col state format a11 set lines 120 select GLOBALID, BRANCHID, PREPARECOUNT PREP, STATE, COUPLING from v$global_transaction;
GLOBALID BRANCHID PREP STATE -------- -------------------------------- ---- ----------- 00000001 00000000000000000000000000000002 0 COMBINATION 00000001 00000000000000000000000000000003 0 COMBINATION
|
Session 2 |
declare tx dbms_xa_xid; begin tx:=dbms_xa_xid( hextoraw('00000001'), hextoraw('00000000000000000000000000000002')); :rc:=dbms_xa.xa_prepare( tx); end; /
print rc
RC ---- 3
col globalid format a8 col branchid format a32 col prep format 999 col state format a11 set lines 120 select GLOBALID, BRANCHID, PREPARECOUNT PREP, STATE, COUPLING from v$global_transaction;
GLOBALID BRANCHID PREP STATE -------- -------------------------------- ---- ----------- 00000001 00000000000000000000000000000003 0 COMBINATION
|
Session 3 |
declare tx dbms_xa_xid; begin tx:=dbms_xa_xid( hextoraw('00000001'), hextoraw('00000000000000000000000000000003')); :rc:=dbms_xa.xa_prepare( tx); end; /
print rc
RC ---- 0
col globalid format a8 col branchid format a32 col prep format 999 col state format a11 set lines 120 select GLOBALID, BRANCHID, PREPARECOUNT PREP, STATE, COUPLING from v$global_transaction;
GLOBALID BRANCHID PREP STATE -------- -------------------------------- ---- ----------- 00000001 00000000000000000000000000000003 1 COMBINATION
|
Session 3 |
declare tx dbms_xa_xid; begin tx:=dbms_xa_xid( hextoraw('00000001'), hextoraw('00000000000000000000000000000003')); :rc:=dbms_xa.xa_commit( tx, false); end; /
print rc
RC ---- 0
col globalid format a8 col branchid format a32 col prep format 999 col state format a11 set lines 120 select GLOBALID, BRANCHID, PREPARECOUNT PREP, STATE, COUPLING from v$global_transaction;
no rows selected
col id format 999 select * from x;
ID ---- 3 1 2
|
Pour en savoir plus, regardez :
- Oracle® Database Advanced Application Developer’s Guide
11g Release 2 (11.2)
15 Developing Applications with Oracle XA
Using the DBMS_XA Package
- Oracle® Database PL/SQL Packages and Types Reference
11g Release 2 (11.2)
162 DBMS_XA
Note :
Il est possible de mettre en œuvre une technique semblable sur des clusters RAC. Néanmoins, il y a d’autres considérations à prendre en compte. Un sujet pour un prochain article peut-être ?
Limites de l’approche
Cette approche souffre d’une limite due à la coordination d’un enqueue lié aux transactions distribuées (DX). Autrement dit, si cette méthode fonctionne bien, oubliez-la si votre objectif est d’améliorer les performances de votre application en parallélisant ces traitements (au moins en 11.2) ; Les 2 exemples ci-dessous illustrent que c’est le cas, même en lecture et sur des objets ne participant pas à la transaction distribuée. Pour cela, nous allons créer une table X :
drop table x purge;
create table x (id number);
- Exemple 1: A priori forte contention de la transaction distribuée
Dans cet exemple, vous manipulez exclusivement les objets faisant partie de la transaction distribuée. Vous lancerez le code ci-dessous simultanément dans 2 sessions :
set timing on
begin
for i in 1..1000000 loop
insert into x values (i);
end loop;
end;
/
- Exemple 2: A priori plus faible contention de la transaction distribuée
Dans ce second exemple, vous manipulez plutôt des lectures sur des objets ne participant pas réellement à la transaction distribuée (DBA_OBJECTS
). Vous lancerez le code ci-dessous simultanément dans 2 sessions :
set timing on
declare
z number;
begin
for i in 1..1000 loop
insert into x values (i);
select count(*) into z from dba_objects;
end loop;
end;
/
Le tableau ci-dessous récapitulent les temps obtenu dans un environnement de test Oracle 11.2 :
Scénario |
Exemple 1 |
Exemple 2 |
2 sessions en parallèle non coordonnées |
37 » |
21 » |
2 sessions en parallèle dans la même transaction distribuée |
1’16 » |
42 » |
En regardant les activités principales des cas d’utilisation de transactions distribuées avec Statspack ou AWR, on observe dans les « Top Events » quelque chose comme ceci :
Avg
%Time Total Wait wait Waits % DB
Event Waits -outs Time (s) (ms) /txn time
-------------------------- ------------ ----- ---------- ------- -------- ------
enq: DX - contention 13 77 65 4976 0.3 42.6
inactive transaction branc 10 100 10 1001 0.2 6.6
[...]
-------------------------------------------------------------
Conclusion
Ce comportement est différent dans le cas où les branches sont « loosely coupled » comme, votre expérience de SQL*Loader en parallèle vous le laisse imaginer… Cela étant, je n’ai pas encore trouvé le moyen d’utiliser DBMS_XA
dans ce mode pour vérifier que ça change les performances. En outre, dans ce cas, les changements ne seraient pas visibles entre branches/sessions de la transaction globale…
Quelqu’un sait-il utiliser DBMS_XA
pour des branches « loosely coupled » dans la même instance et simplement ?