Rechercher sur arkzoyd.com

29 janvier 2012

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énarioExemple 1Exemple 2
    2 sessions en parallèle non coordonnées37''21''
    2 sessions en parallèle dans la même transaction distribuée1'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 ?

    Aucun commentaire: