Utilisation et limites d'Oracle 11g DBMS_XA

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;
    /
    • Résultats

    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 ?