IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

SQL Oracle Discussion :

La résolution du problème de la table mutante introduit un bug


Sujet :

SQL Oracle

  1. #1
    Expert éminent sénior Avatar de mnitu
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Octobre 2007
    Messages
    5 611
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Marne (Champagne Ardenne)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Octobre 2007
    Messages : 5 611
    Points : 11 252
    Points
    11 252
    Par défaut La résolution du problème de la table mutante introduit un bug
    J'ai déjà posté ça, mais vu le manque de réaction je me demande si quelqu'un à lu et compris où est le problème.
    Donc je vous proposé de reprendre la solution pour contourner l’erreur induite par la table en mutation (http://sgbd.developpez.com/oracle/ora-04091/):

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
     
     
    CREATE TABLE CLIENT(
        IDC INTEGER PRIMARY KEY ,
        NOM VARCHAR2 (40)
        )
    /
    CREATE TABLE VOYAGE(
        IDV INTEGER PRIMARY KEY ,
        DESTINATION VARCHAR2 (40),
        MAXPLACE INTEGER , -- nombre total de places
        PLACEDISPO INTEGER ) -- nombre de places disponibles     
    /
     
    CREATE TABLE INSCRIPTION(
        IDC INTEGER REFERENCES CLIENT(IDC),
        IDV INTEGER REFERENCES VOYAGE(IDV),
        DATERESERV DATE ,
        CONSTRAINT INSCRIPTION_PK PRIMARY KEY (IDC, IDV)
    )
    /
    INSERT INTO CLIENT(IDC, NOM) VALUES (1, 'DURAND');
    INSERT INTO CLIENT(IDC, NOM) VALUES (2, 'DUBOIS');
    INSERT INTO CLIENT(IDC, NOM) VALUES (3, 'DUGENOU');
    COMMIT ;
     
    -- un seul place disponoble pour aller à VENISE
    INSERT INTO VOYAGE(IDV, DESTINATION, MAXPLACE, PLACEDISPO) VALUES (10, 'VENISE', 25, 1);
    INSERT INTO VOYAGE(IDV, DESTINATION, MAXPLACE, PLACEDISPO) VALUES (11, 'PRAGUE', 20, 0);
    COMMIT ;
     
    -- déclencheur qui n'interroge plus la table mutante
    CREATE OR REPLACE TRIGGER TRIG_INSCRIPTION BEFORE INSERT ON INSCRIPTION FOR EACH ROW
    DECLARE
        NB_DISPO INTEGER ;
    BEGIN
        SELECT PLACEDISPO INTO NB_DISPO FROM VOYAGE
        WHERE IDV=:NEW.IDV;
        IF NB_DISPO < 1 THEN
            DBMS_OUTPUT.PUT_LINE('Désolé, voyage complet');
        ELSE
            UPDATE VOYAGE SET PLACEDISPO=PLACEDISPO - 1
            WHERE IDV=:NEW.IDV;
        END IF ;
    END ;
    /
    Il y a une seule modification concernant le nombre de place disponible pour aller à Venise.

    Ensuite j’ouvre une première session et j'ajoute une première inscription sans faire la validation (commit) :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    -- DUGENOU aimerait bien aller à Venise :
    INSERT INTO INSCRIPTION(IDC, IDV, DATERESERV) SELECT 3, 10, TO_DATE(SYSDATE, 'DD/MM/YYYY') FROM DUAL ;
    Dans une autre session j'ajoute une deuxième inscription pour la même destination
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    -- DUBOIS aimerait lui aussi aller à Venise :
    INSERT INTO INSCRIPTION(IDC, IDV, DATERESERV) SELECT 2, 10, TO_DATE(SYSDATE, 'DD/MM/YYYY') FROM DUAL ;
    Enfin je valide dans chaque session (commit) et j’interroge la table du voyage :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
     
    SQL> SELECT * FROM voyage
      2  ;
     
           IDV DESTINATION                                MAXPLACE PLACEDISPO
    ---------- ---------------------------------------- ---------- ----------
            10 VENISE                                           25         -1
            11 PRAGUE                                           20          0
    Autrement dit le trigger n'arrive pas à faire son boulot. Pour cella il faut reserver l'enregistrement de la table de voyage en mode pessimiste ou optimiste.

    Il est intéressant de noter que la solution proposé par http://www.akadia.com/services/ora_m..._problems.html introduit le même type de problème.

  2. #2
    McM
    McM est déconnecté
    Expert éminent

    Homme Profil pro
    Développeur Oracle
    Inscrit en
    Juillet 2003
    Messages
    4 580
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur Oracle

    Informations forums :
    Inscription : Juillet 2003
    Messages : 4 580
    Points : 7 740
    Points
    7 740
    Billets dans le blog
    4
    Par défaut
    Moi je ferais dans tous les cas l'update dans VOYAGE (en lui ayant rajouté une contrainte sur PLACEDISPO > 0).
    Tu économises 1 select et surtout tu n'auras plus ce cas au commit.
    More Code : More Bugs. Less Code : Less Bugs
    Mon Blog PL/Sql : Fichier Zip / Image BMP / Lire sqliteDB / QRCode et Images PNG ou BMP

  3. #3
    Expert éminent sénior Avatar de mnitu
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Octobre 2007
    Messages
    5 611
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Marne (Champagne Ardenne)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Octobre 2007
    Messages : 5 611
    Points : 11 252
    Points
    11 252
    Par défaut
    Et pourquoi pas? Comme au bout de la ligne le résultat est correcte c'est une bonne solution. Quoi que je ne peux pas m'empêcher de remarquer que la solution que vous proposez est un détail d'implémentation d'un verrou (lock) optimiste .

    Mais je vais continuer à illustrer mes idées avec la suite; cette fois il s'agit de la solution basée sur la méthode dite "des deux déclencheurs avec table temporaire"
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
     
    CREATE TABLE CLIENT(
        IDC INTEGER PRIMARY KEY ,
        NOM VARCHAR2 (40));
     
    CREATE TABLE VOYAGE(
        IDV INTEGER PRIMARY KEY ,
        DESTINATION VARCHAR2 (40),
        MAXPLACE INTEGER ) -- nombre total de places     
    ;
     
    CREATE TABLE INSCRIPTION(
        IDC INTEGER REFERENCES CLIENT(IDC),
        IDV INTEGER REFERENCES VOYAGE(IDV),
        DATERESERV DATE ,
        CONSTRAINT INSCRIPTION_PK PRIMARY KEY (IDC, IDV));
     
    INSERT INTO CLIENT(IDC, NOM) VALUES (1, 'DURAND');
    INSERT INTO CLIENT(IDC, NOM) VALUES (2, 'DUBOIS');
    INSERT INTO CLIENT(IDC, NOM) VALUES (3, 'DUGENOU');
    COMMIT ;
     
    INSERT INTO VOYAGE(IDV, DESTINATION, MAXPLACE) VALUES (10, 'VENISE', 1);
    INSERT INTO VOYAGE(IDV, DESTINATION, MAXPLACE) VALUES (11, 'PRAGUE', 20);
    COMMIT ;
     
    -- Création d'une table temporaire vide de même structure que INSCRIPTION
    CREATE GLOBAL TEMPORARY TABLE TEMP_INSCRIPTION AS SELECT * FROM INSCRIPTION WHERE 0=1;
     
    -- 1er déclencheur, de niveau ligne, qui n'interroge plus la table mutante
    -- à la place, il stocke dans la table temporaire les données insérées
    CREATE OR REPLACE TRIGGER TRIG_INSCRIPTION BEFORE INSERT ON INSCRIPTION FOR EACH ROW 
    BEGIN 
        INSERT INTO TEMP_INSCRIPTION(IDC, IDV, DATERESERV) VALUES (:NEW.IDC, :NEW.IDV, :NEW.DATERESERV);
    END ;
    /
     
    -- second déclencheur, de niveau instruction, qui vérifie qu'il y a des places libres
    -- il s'exécute une seule fois, après le traitement de tous les enregistrements touchés par l'INSERT sous-jacent
    CREATE OR REPLACE TRIGGER TRIG_INSCRIPTION2 AFTER INSERT ON INSCRIPTION
    DECLARE 
        NB_RESERVE INTEGER ; -- nombre de réservations déjà faites
        NB_MAXPLACE INTEGER ; -- nombre de places total     
    BEGIN 
        FOR LIGNE IN (SELECT * FROM TEMP_INSCRIPTION ORDER BY DATERESERV) LOOP 
            SELECT COUNT (*) INTO NB_RESERVE FROM INSCRIPTION 
            WHERE IDV=LIGNE.IDV;
            SELECT MAXPLACE INTO NB_MAXPLACE FROM VOYAGE 
            WHERE IDV=LIGNE.IDV;
            IF NB_MAXPLACE - NB_RESERVE < 0 THEN 
                Raise_application_error(-20000,'Réservation impossible pour voyage ' || LIGNE.IDV || ' et client ' || LIGNE.IDC);
                -- on supprime les inscriptions excédentaires
                DELETE FROM INSCRIPTION WHERE IDV=LIGNE.IDV AND IDC=LIGNE.IDC;
            END IF ;
        END LOOP ;
        -- tout à la fin, on remet à zéro la table temporaire. A noter qu'un TRUNCATE n'est pas possible ici, car il débuterait une nouvelle transaction
        DELETE FROM TEMP_INSCRIPTION;
    END ;
    /
    Comme avant, les modifications sont minimales, un seul place pour aller a Venise en tout (mais oui il faut se préparer pour les voyage à l'époque du vélo ) et le trigger soulève une exception à la place d'un message.

    Ensuite une inscription non validé saisie dans une session
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    -- DUGENOU aimerait bien aller à Venise :
    INSERT INTO INSCRIPTION(IDC, IDV, DATERESERV) SELECT 3, 10, TO_DATE(SYSDATE, 'DD/MM/YYYY') FROM DUAL ;
    et une deuxième dans une autre session
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    INSERT INTO INSCRIPTION(IDC, IDV, DATERESERV) SELECT 2, 10, TO_DATE(SYSDATE, 'DD/MM/YYYY') FROM DUAL ;
    Et en fin on valide une après l'autre dans les deux sessions et on interroge la table des inscriptions et de voyage
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
     
    SQL> select * from inscription;
     
           IDC        IDV DATERESE
    ---------- ---------- --------
             2         10 05/11/07
             3         10 05/11/07
     
    SQL> select * from voyage;
     
           IDV DESTINATION                                MAXPLACE
    ---------- ---------------------------------------- ----------
            10 VENISE                                            1
            11 PRAGUE                                           20
     
    SQL>
    Encore une fois, deux réservations pour un seul place libre.
    Et bien, la solution telle qu'elle est actuellement, tout simplement ne fonctionne pas! Avez vous aussi une proposition pour ce cas ?
    (PS. le lien correct est http://sgbd.developpez.com/oracle/ora-04091/)

  4. #4
    Expert éminent sénior
    Avatar de orafrance
    Profil pro
    Inscrit en
    Janvier 2004
    Messages
    15 967
    Détails du profil
    Informations personnelles :
    Âge : 46
    Localisation : France

    Informations forums :
    Inscription : Janvier 2004
    Messages : 15 967
    Points : 19 073
    Points
    19 073
    Par défaut
    le PRAGMA AUTONOMOUS_TRANSACTION doit fonctionner sans problème

    Pour le 2° cas, je dirais que c'est une erreur du code, j'essayerai de voir ça aujourd'hui... le trigger traite toutes les lignes de la table temporaire au lieu de ne prendre que la ligne intéressante... et d'ailleurs, comment l'autre session peut voir les lignes de la 1° sans commit

    Essaye de remplacer la table temporaire par une table normal STP ou au moins ajoute la propriété : ON COMMIT DELETE ROWS

    Edit : en fait, c'est plutôt le contraire... dans ta session tu ne peux pas voir la ligne ajoutée par la 1° à moins de faire un commit... c'est bien une erreur de ton code

  5. #5
    Expert éminent sénior
    Avatar de orafrance
    Profil pro
    Inscrit en
    Janvier 2004
    Messages
    15 967
    Détails du profil
    Informations personnelles :
    Âge : 46
    Localisation : France

    Informations forums :
    Inscription : Janvier 2004
    Messages : 15 967
    Points : 19 073
    Points
    19 073
    Par défaut
    je teste et finalement, me rends compte que je dis n'importe quoi

    1° cas : ça fonctionne parfaitement, le message s'affiche bien au 2° insert. Bien entendu, il faut ajouté un RAISE pour faire tomber le trigger en erreur et donc éviter d'insérer la ligne dans INSCRIPTION

    Idem pour le 2° cas. Dans l'exemple on cherche pas a interrompre l'insert mais à alerter l'utilisateur. Ajoute un RAISE si tu veut que l'insertion ne se produise pas.

    remplace DBMS_OUTPUT( par RAISE_APPLICATION_ERROR(-20001,

  6. #6
    Expert éminent sénior Avatar de mnitu
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Octobre 2007
    Messages
    5 611
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Marne (Champagne Ardenne)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Octobre 2007
    Messages : 5 611
    Points : 11 252
    Points
    11 252
    Par défaut
    Dans le deux cas il suffit de tester comme je vous le propose pour voire que cella ne marche pas ! Si vous regarder le deuxième cas j’ai remplacé bien le dbms_output par RAISE (et je l'ai fait aussi dans mon première test mais je repris le code telle qu'il est).
    Est-ce que vous avez vraiment testé avec deux sessions comme je l’indique ?
    Et je ne cherche pas une solution, j'essai de vous démontrer toute simplement que les solutions proposées ne fonctionnent pas.
    Le PRAGMA_AUTONOMUS_TRANSACTION doit fonctionner sans problème
    Désolé, mais « Perdu »

  7. #7
    Expert éminent sénior
    Avatar de orafrance
    Profil pro
    Inscrit en
    Janvier 2004
    Messages
    15 967
    Détails du profil
    Informations personnelles :
    Âge : 46
    Localisation : France

    Informations forums :
    Inscription : Janvier 2004
    Messages : 15 967
    Points : 19 073
    Points
    19 073
    Par défaut
    j'ai testé dans la même session... je creuserai à l'occasion mais sache que ces techniques sont régulièrement utilisées et que jusqu'alors j'ai pas souvenir d'avoir rencontré un quelconque problème, c'est pourquoi je mise plutôt pour une coquille dans ton process

  8. #8
    Membre expert
    Avatar de LeoAnderson
    Profil pro
    Inscrit en
    Septembre 2004
    Messages
    2 938
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2004
    Messages : 2 938
    Points : 3 199
    Points
    3 199
    Par défaut
    et surtout, vous avez bien rajouté le raise après le dbms_output ?

    parce que sinon, vous affichez un message de débug sans rien faire
    il est plus judicieux de définir votre propre exception...

  9. #9
    Expert éminent sénior Avatar de mnitu
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Octobre 2007
    Messages
    5 611
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Marne (Champagne Ardenne)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Octobre 2007
    Messages : 5 611
    Points : 11 252
    Points
    11 252
    Par défaut
    Citation Envoyé par LeoAnderson Voir le message
    et surtout, vous avez bien rajouté le raise après le dbms_output ?

    parce que sinon, vous affichez un message de débug sans rien faire
    il est plus judicieux de définir votre propre exception...
    J'ai fourni un exemple facilement réproductible, c'est du copier coller. Il vous prendre 2 minute de le tester vous même . Et au bout du compte ne vous raccrochez pas du DBMS_OUTPUT. Il est facile de voir que le message ne s'affiche pas pourvu que vous n'avez pas oubliez de activer serveroutput.
    Je suis tellement désolé, mais je ne suis pas tombé sur l'erreur par hasard. J'ai construit mon jeux d'essai parce que j'ai compris pourquoi cella ne tient pas la route.

  10. #10
    Expert éminent sénior
    Avatar de orafrance
    Profil pro
    Inscrit en
    Janvier 2004
    Messages
    15 967
    Détails du profil
    Informations personnelles :
    Âge : 46
    Localisation : France

    Informations forums :
    Inscription : Janvier 2004
    Messages : 15 967
    Points : 19 073
    Points
    19 073
    Par défaut
    OK, j'avais pas fait le test dans 2 sessions. Comme je disais, le PRAGMA résouds le problème parce que sinon il manque un COMMIT.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    CREATE OR REPLACE TRIGGER TRIG_INSCRIPTION BEFORE INSERT ON INSCRIPTION FOR EACH 
     
    DECLARE
        NB_DISPO INTEGER ;
        PRAGMA AUTONOMOUS_TRANSACTION;
    BEGIN
        SELECT PLACEDISPO INTO NB_DISPO FROM VOYAGE
        WHERE IDV=:NEW.IDV;
        IF NB_DISPO < 1 THEN
            DBMS_OUTPUT.PUT_LINE('Désolé, voyage complet');
        ELSE
            UPDATE VOYAGE SET PLACEDISPO=PLACEDISPO - 1
            WHERE IDV=:NEW.IDV;
            COMMIT;
        END IF ;
    END ;
    /
    avec ce trigger le 1° test fonctionne et j'imagine qu'il en est de même pour les autres

    PS : note que dans ce cas, si l'INSERT de la 1° session est rollbacké tu perds une place quand même. Pour conclure, les méthodes présentées fonctionnent mais il convient de régler le problème des accés concurrents avec du NOWAIT par exemple

  11. #11
    Expert éminent sénior Avatar de mnitu
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Octobre 2007
    Messages
    5 611
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Marne (Champagne Ardenne)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Octobre 2007
    Messages : 5 611
    Points : 11 252
    Points
    11 252
    Par défaut
    Comme je l’ai déjà dit la solution PRAGMA AUTONOUMUS TRANSACTION est complètement erronée ! Vous venez de remarquer vous même que vous perdez un place en cas de rollback. Autrement dite il y avais 25 place au début pour aller à Venise mais le trigger a perdu 3. Ca doit être le programmeur donc qui devrait les payer .

    Non vos méthodes ne fonctionnent pas, au mieux elles fonctionnent en isolation ou pour des autres base de données que Oracle; vous avez oublié l’aspect concurrentiel. Et c’est pour cela que de le départe j’utilise deux sessions.

    Et oui, il manque le verrou (lock) optimiste ou pessimiste, pas forcement le NOWAIT ! Et oui, si vous pouvez encore vous en sortir avec la méthode 1 avec la méthode deux c’est pire : pour que vous y arrivez il va falloir verrouiller toute la table ou perdre des places avec PRAGMA AUTONOUMUS TRANSACTION !

    Est-ce que c’est acceptable ?

  12. #12
    Expert éminent sénior
    Avatar de orafrance
    Profil pro
    Inscrit en
    Janvier 2004
    Messages
    15 967
    Détails du profil
    Informations personnelles :
    Âge : 46
    Localisation : France

    Informations forums :
    Inscription : Janvier 2004
    Messages : 15 967
    Points : 19 073
    Points
    19 073
    Par défaut
    On dit la même chose au final

    Les solutions fonctionnent pour peu que le programmeur a pris soin de positionner un lock explicite pour gérer la concurrence d'accés. Et une reprise sur erreur doit aussi être gérer (le ROLLBACK)... mais bon, là c'est des problèmatiques fonctionnelles, on est loin du simple aspect technique consistant à éviter la mutation de table (tu peux aussi mettre un PRAGMA parce que le trigger ne fait qu'un SELECT et que tu veux éviter l'erreur Oracle).

  13. #13
    Expert éminent sénior Avatar de mnitu
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Octobre 2007
    Messages
    5 611
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Marne (Champagne Ardenne)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Octobre 2007
    Messages : 5 611
    Points : 11 252
    Points
    11 252
    Par défaut
    Citation Envoyé par orafrance Voir le message
    On dit la même chose au final
    Si par cella vous comprenez que les exemples fournis ne fonctionnent pas je suis d’accord avec vous. Sinon j’attends vos versions qui pourraient fonctionner.

  14. #14
    Expert éminent sénior
    Avatar de orafrance
    Profil pro
    Inscrit en
    Janvier 2004
    Messages
    15 967
    Détails du profil
    Informations personnelles :
    Âge : 46
    Localisation : France

    Informations forums :
    Inscription : Janvier 2004
    Messages : 15 967
    Points : 19 073
    Points
    19 073
    Par défaut
    en effet, ça ne fonctionne pas... toujours sauf qu'il n'y a aucune solution qui répond à tous les cas. Tu as mis le doigt sur un pré-requis qui n'est pas signalé : un lock explicite doit être positionné sur la table pour éviter qu'une autre session commence alors que la 1° n'est pas fini et donc réserve des places déjà prises.

    En cela, je dirais que ce n'est pas un bug, mais une fonctionnalité

    Pour éviter le lock explicite on peut utiliser le PRAGMA mais dans ce cas il faut aussi développer une solution pour rollbacker ce que le trigger à commiter quand la transaction principale est rollbacké.

    Edit : tu noteras que le programmeur qui développerait un programme de la sorte sans tenir compte des concurrences d'accés serait bien avisé de songer à faire un autre métier, ça parait quand même évident qu'il faut faire le nécessaire pour gérer convenablement les réservations simultanées... je pense que c'est pourquoi ce n'est pas préciser dans le tutoriel dont tu fais référence

  15. #15
    Expert éminent sénior
    Avatar de orafrance
    Profil pro
    Inscrit en
    Janvier 2004
    Messages
    15 967
    Détails du profil
    Informations personnelles :
    Âge : 46
    Localisation : France

    Informations forums :
    Inscription : Janvier 2004
    Messages : 15 967
    Points : 19 073
    Points
    19 073
    Par défaut
    Citation Envoyé par mnitu Voir le message
    Sinon j’attends vos versions qui pourraient fonctionner.
    Voila un exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    CREATE OR REPLACE TRIGGER TRIG_INSCRIPTION BEFORE INSERT ON INSCRIPTION 
    FOR EACH ROW
    DECLARE
        NB_DISPO INTEGER ;
    BEGIN
        NB_DISPO := nb_places(:NEW.IDV);
        IF NB_DISPO < 1 THEN
            DBMS_OUTPUT.PUT_LINE('Désolé, voyage complet');
        ELSE
            UPDATE VOYAGE SET PLACEDISPO=PLACEDISPO - 1
            WHERE IDV=:NEW.IDV;
        END IF ;
    END ;
    /
     
    CREATE OR REPLACE FUNCTION nb_places(p_IDV NUMBER) RETURN NUMBER IS
    PRAGMA AUTONOMOUS_TRANSACTION;
    NB_DISPO INTEGER ;
    BEGIN
      SELECT PLACEDISPO INTO NB_DISPO FROM VOYAGE
      WHERE IDV=p_IDV;
     
    	RETURN NB_DISPO;
    END;
    /
    Le PRAGMA concernait tout le trigger alors qu'on en a uniquement besoin pour le SELECT. Voila qui évite de devoir locker la table

  16. #16
    Expert éminent sénior Avatar de mnitu
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Octobre 2007
    Messages
    5 611
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Marne (Champagne Ardenne)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Octobre 2007
    Messages : 5 611
    Points : 11 252
    Points
    11 252
    Par défaut
    Citation Envoyé par orafrance Voir le message

    Edit : tu noteras que le programmeur qui développerait un programme de la sorte sans tenir compte des concurrences d'accés serait bien avisé de songer à faire un autre métier, ça parait quand même évident qu'il faut faire le nécessaire pour gérer convenablement les réservations simultanées... je pense que c'est pourquoi ce n'est pas préciser dans le tutoriel dont tu fais référence
    Avec ça je suis bien d'accord (non, mais je rêve!)
    Ca explique pourquoi vous avez bien pris soin de ne pas le gérer dans votre exemple!

    Si vous voulez mon opinion sincère (mais j'ai de doute que vous en voulez) c'est plus correcte de dire OK on s'est trompé, et nous allons corriger le bug que vous nous avez signalé, que de continuer de défendre votre position.

  17. #17
    Expert éminent sénior Avatar de mnitu
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Octobre 2007
    Messages
    5 611
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Marne (Champagne Ardenne)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Octobre 2007
    Messages : 5 611
    Points : 11 252
    Points
    11 252
    Par défaut
    Et comme je pensais çà ne marche pas
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
     
    drop table inscription
    /
    drop table voyage
    /
    drop table client
    /
     
    CREATE TABLE CLIENT(
        IDC INTEGER PRIMARY KEY ,
        NOM VARCHAR2 (40));
        CREATE TABLE VOYAGE(
        IDV INTEGER PRIMARY KEY ,
        DESTINATION VARCHAR2 (40),
        MAXPLACE INTEGER , -- nombre total de places
        PLACEDISPO INTEGER ) -- nombre de places disponibles     
    ;
     
    CREATE TABLE INSCRIPTION(
        IDC INTEGER REFERENCES CLIENT(IDC),
        IDV INTEGER REFERENCES VOYAGE(IDV),
        DATERESERV DATE ,
        CONSTRAINT INSCRIPTION_PK PRIMARY KEY (IDC, IDV));
     
    INSERT INTO CLIENT(IDC, NOM) VALUES (1, 'DURAND');
    INSERT INTO CLIENT(IDC, NOM) VALUES (2, 'DUBOIS');
    INSERT INTO CLIENT(IDC, NOM) VALUES (3, 'DUGENOU');
    COMMIT ;
     
    INSERT INTO VOYAGE(IDV, DESTINATION, MAXPLACE, PLACEDISPO) VALUES (10, 'VENISE', 25, 1);
    INSERT INTO VOYAGE(IDV, DESTINATION, MAXPLACE, PLACEDISPO) VALUES (11, 'PRAGUE', 20, 0);
    COMMIT ;
     
    CREATE OR REPLACE FUNCTION nb_places(p_IDV NUMBER) RETURN NUMBER IS
    PRAGMA AUTONOMOUS_TRANSACTION;
    NB_DISPO INTEGER ;
    BEGIN
      SELECT PLACEDISPO INTO NB_DISPO FROM VOYAGE
      WHERE IDV=p_IDV;
     
    	RETURN NB_DISPO;
    END;
    /
     
    CREATE OR REPLACE TRIGGER TRIG_INSCRIPTION BEFORE INSERT ON INSCRIPTION 
    FOR EACH ROW
    DECLARE
        NB_DISPO INTEGER ;
    BEGIN
        NB_DISPO := nb_places(:NEW.IDV);
        IF NB_DISPO < 1 THEN
            Raise_Application_Error(-20000,'Désolé, voyage complet');
        ELSE
            UPDATE VOYAGE SET PLACEDISPO=PLACEDISPO - 1
            WHERE IDV=:NEW.IDV;
        END IF ;
    END ;
    /
     
    -- DUGENOU aimerait bien aller à Venise :
    INSERT INTO INSCRIPTION(IDC, IDV, DATERESERV) SELECT 3, 10, TO_DATE(SYSDATE, 'DD/MM/YYYY') FROM DUAL ;
     
    -- DUBOIS aimerait bien aller à Venise :
    INSERT INTO INSCRIPTION(IDC, IDV, DATERESERV) SELECT 2, 10, TO_DATE(SYSDATE, 'DD/MM/YYYY') FROM DUAL ;
     
    SQL> select * from voyage;
     
           IDV DESTINATION                                MAXPLACE PLACEDISPO
    ---------- ---------------------------------------- ---------- ----------
            10 VENISE                                           25         -1
            11 PRAGUE                                           20          0
     
    SQL>
    Comme d'habitude il y a un seul place pour aller à Venise et j'ai mis Raise à la place de DBMS_OUTPUT.

    Et n'oubliez pas d'exécuter les deux inserts une après l'autre dans deux sessions différentes et ensuite de valider.

  18. #18
    Membre expert
    Avatar de LeoAnderson
    Profil pro
    Inscrit en
    Septembre 2004
    Messages
    2 938
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2004
    Messages : 2 938
    Points : 3 199
    Points
    3 199
    Par défaut
    Citation Envoyé par mnitu Voir le message
    Avec ça je suis bien d'accord (non, mais je rêve!)
    Ca explique pourquoi vous avez bien pris soin de ne pas le gérer dans votre exemple!

    Si vous voulez mon opinion sincère (mais j'ai de doute que vous en voulez) c'est plus correcte de dire OK on s'est trompé, et nous allons corriger le bug que vous nous avez signalé, que de continuer de défendre votre position.
    ça se voit que tu connais pas Orafrance !

  19. #19
    Expert éminent sénior
    Avatar de orafrance
    Profil pro
    Inscrit en
    Janvier 2004
    Messages
    15 967
    Détails du profil
    Informations personnelles :
    Âge : 46
    Localisation : France

    Informations forums :
    Inscription : Janvier 2004
    Messages : 15 967
    Points : 19 073
    Points
    19 073
    Par défaut
    Citation Envoyé par mnitu Voir le message
    Avec ça je suis bien d'accord (non, mais je rêve!)
    Si tu trouves une autre méthode, propose la donc. Aucune solution ne fonctionne sans limitation simplement parce que le niveau d'isolation READ UNCOMMITTED n'existe pas sous Oracle...

    Si c'est trop demandé que d'adapter la solution aux nombreux cas qui peuvent nécessité ce besoin alors effectivement, il y a un problème

    Citation Envoyé par mnitu Voir le message
    Ca explique pourquoi vous avez bien pris soin de ne pas le gérer dans votre exemple!

    Si vous voulez mon opinion sincère (mais j'ai de doute que vous en voulez) c'est plus correcte de dire OK on s'est trompé, et nous allons corriger le bug que vous nous avez signalé, que de continuer de défendre votre position.
    D'abord, je ne suis pas l'auteur de l'article, je ne le modifierais donc pas personnellement. Par ailleurs tous les articles sont soumis à correction auprès du collége de rédacteurs qui compose l'équipe rédactionnel. Si tu as une idée lumineuse pour trouver une solution qui fonctionne dans tous les cas possibles, alors je serais ravi de la relayer à l'auteur pour corriger son article
    Si tu n'as pas d'idée mais que tu sais rédiger une addendum pour expliquer les précautions à prendre, je suis également preneur.

    Dans l'attente, n'étant pas convaincu que ce soit utile, je ne me ferais pas l'écho de tes remarques, je t'invite donc à contacter Pomalaix si tu veux lui soumettre ton cas de test.

    Citation Envoyé par mnitu Voir le message
    Comme d'habitude il y a un seul place pour aller à Venise et j'ai mis Raise à la place de DBMS_OUTPUT.

    Et n'oubliez pas d'exécuter les deux inserts une après l'autre dans deux sessions différentes et ensuite de valider.
    J'avoue avoir testé rapidement (faut bien que je bosse quand même ) et j'ai pu ne pas avoir initialiser les tables correctement, je vérifierai demain... en attendant, j'apprécierai que tu mesures tes propos alors même que j'essaye de comprendre ce qui ne va pas pour améliorer cette solution

  20. #20
    Expert éminent sénior
    Avatar de orafrance
    Profil pro
    Inscrit en
    Janvier 2004
    Messages
    15 967
    Détails du profil
    Informations personnelles :
    Âge : 46
    Localisation : France

    Informations forums :
    Inscription : Janvier 2004
    Messages : 15 967
    Points : 19 073
    Points
    19 073
    Par défaut
    Citation Envoyé par mnitu Voir le message
    Et n'oubliez pas d'exécuter les deux inserts une après l'autre dans deux sessions différentes et ensuite de valider.
    On est bien d'accord, la 2° session se retrouve lockée tant qu'on a pas mis fin à la 1° transaction ? C'est bien le test que j'ai fait pourtant

    Comme je disais, je vérifierai demain en prenant garde aux données de mes tables et posterais l'intégralité du cas de test.

    Demain j'essayerai aussi une vue avec un trigger INSTEAD OF INSERT ça me semble être une piste intéressante

    Edit : OK, j'ai compris mon erreur, j'vais voir ça

+ Répondre à la discussion
Cette discussion est résolue.
Page 1 sur 2 12 DernièreDernière

Discussions similaires

  1. Résoudre le problème de la table mutante
    Par mnitu dans le forum PL/SQL
    Réponses: 2
    Dernier message: 23/04/2010, 08h43
  2. problème de la table mutante (ora-04091)
    Par ando0098 dans le forum PL/SQL
    Réponses: 0
    Dernier message: 19/04/2010, 10h41
  3. Problème table Mutante
    Par shadeoner dans le forum PL/SQL
    Réponses: 4
    Dernier message: 19/09/2008, 08h21
  4. Trigger, problème de table mutante
    Par waukine dans le forum PL/SQL
    Réponses: 7
    Dernier message: 24/04/2008, 16h03
  5. Réponses: 4
    Dernier message: 30/10/2005, 09h13

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo