17.5.3 Transactions et savePoint

Des transactions et des points de sauvegarde de base de donnés peuvent être utilisés afin de confirmer ou de revenir à un point de sauvegarde.

Ces transactions n'ont aucun effet sur l'objet mémoire manipulé.

Les méthodes DbObj::savePoint(), DbObj::rollbackPoint() et DbObj::commitPoint() permettent de gérer les transactions et les points de retours.

Ces méthodes utilisent, les fonctions savepoint, rollback to savepoint, release savepoint de Postgresql.

La méthode savePoint() ouvre automatiquement une transaction (BEGIN), si celle-ci n'est pas encore ouverte. Les méthodes rollbackPoint() et commitPoint() ferment la transaction si le point de sauvegarde est le premier des points posés.

Les points de sauvegarde ne sont pas liés à un objet mais impactent toutes les requêtes utilisant la même ressource de connexion à la base. Dynacase n'ouvre qu'une seule ressource de connexion, par conséquent toutes les requêtes utilisant l'api sont impactées (Doc::store(), simpleQuery, DbObj::modify(), ...).

Lorqu'un premier savePoint est déclaré, il faut obligatoirement avoir un commitPoint ou un rollbackPoint du premier point. Dans le cas contraire toutes les modifications sur la base de données, depuis le premier point de sauvegarde, sont abandonnées.

Les méthodes de point de sauvegarde sont accessibles depuis tout objet héritant de DbObj notamment les classes Doc et Action.

17.5.3.1 savePoint()

string savePoint(string $point)

Cette méthode ouvre une transaction (BEGIN) si une transaction n'est pas déjà en cours.

Elle ajoute un point de sauvegarde avec l'identifiant donné en paramètre.

Elle retourne une erreur (erreur donnée par postgresSql) si le point de sauvegarde n'a pas pu être posé.

Note : Comme indiqué dans la documentation de postgresql, si le même nom est utilisé, l'index du point de sauvegarde est déplacé.

17.5.3.2 commitPoint()

string commitPoint(string $point)

Cette méthode libère le point de sauvegarde de l'identifiant donné en paramètre.

Si le point de sauvegarde est le premier point alors la transaction est confirmée (COMMIT);

Elle retourne une erreur si le point de sauvegarde n'a pas été posé au préalable.

17.5.3.3 rollbackPoint()

string rollbackPoint(string $point)

Cette méthode retourne au point de sauvegarde de l'identifiant donné en paramètre.

Si le point de sauvegarde est le premier point alors la transaction est abandonnée (ROLLBACK);

Elle retourne une erreur si le point de sauvegarde n'a pas été posé au préalable.

Attention : les documents en mémoires et ceux dans le cache ne sont pas affectés par cette méthode. Il est conseillé de les ré-initialiser et de vider le cache après utilisation de cette méthode via la fonction clearCacheDoc().

17.5.3.4 lockPoint() Verrouillage de la transaction

string lockPoint(   int $exclusiveLock , 
                 string $exclusiveLockPrefix = '')

3.2.18 La méthode lockPoint indique que la transaction devient exclusive. Un verrou (pg_advisory_xact_lock) est posé sur ce point et un autre processus doit attendre sur ce même point que les données liées à la transaction soient enregistrées sur la base de données. Les paramètres exclusiveLock et exclusiveLockPrefix sont les conditions d'acquisition du verrou. Le paramètre exclusiveLock identifie une ressource (forme numérique) et le paramètre exclusiveLockPrefix indique un contexte (chaîne limitée à 4 caractères).

L'appel de cette méthode doit obligatoirement être fait dans une transaction savePoint. Dans la cas contraire, une exception de type Dcp\Db\Exception est levée.

Le verrou est enlevé lors de la fin de la transaction, c'est à dire lors du commit ou du rollback du premier point.

Ce verrou joue le rôle d'un sémaphore, il bloque l'accès concurrent à une même portion de code.

Exemple :

$document=new_doc("", 1234);
$action->savePoint("myUpdate", $document->initid, "MyUp");
$action->lockPoint($document->initid, "MyUp");
// Un seul processus peut exécuter cette partie pour ce document
$document->setValue("my_reference", 234);
$document->myRecomputeCriticalRelation();
 
$action->commitPoint("myUpdate");
// Le verrou est relaché s'il s'agit du commit du premier save point

17.5.3.5 setMasterLock()

string setMasterLock($useLock)

3.2.18 Le but de cette méthode est d'éviter un nombre important de verrou applicatif. Chaque verrou consomme environ 200 octets de mémoire partagée et la limite max_locks_per_transaction indiqués par postgresql impose que le nombre de verrous soit maîtrisé.

La méthode setMasterLock pose un verrou maître (pg_advisory_lock) et non un "pg_advisory_xact_lock" comme pour le lockPoint. Ce verrou inhibe la pose des verrous activés par l'appel à la méthode lockPoint pour le processus ayant posé le verrou maître. Tous les verrous posés par la méthode lockPoint des autres processus sont bloqués (en attente de la libération du verrou maître). Le verrou maître doit être débloqué par l'appel à la méthode setMasterLock avec en paramètre "false".

Exemple de code possible:

$action->setMasterLock(true); // Activation du verrou - ou attente s'il est posé par un autre processus
for ($i=0;$i<10000;$i++) {
 $action->lockPoint($i,"TST"); // pas de pose réelle de lock pour le processus ayant acquis le verrou maître
         // Mais blocage pour les autres processus qui demande un verrou
}
$action->setMasterLock(false); // Désactivation du verrou

Dynacase Core n'utilise pas ce verrou dans son code.

17.5.3.6 Exemples

17.5.3.6.1 Annulation d'une requête intermédiaire

include_once("FDL/Class.Doc.php");
$document = new_doc("",8160); // Doc dérive de DbObj
$document->savePoint("One");
$document->setValue("us_lname","Test one");
$document->store();  // modification en base doc n°8160 à comme nom "Test One"
 
$document->savePoint("Two");
$document->setValue("us_lname","Test two");
$document->store();// modification en base doc n°8160 à comme nom "Test Two"
$document->rollbackPoint("Two"); // annulation de toutes les requêtes depuis le point "Two"
 
$document->commitPoint("One"); // acquittement des requêtes depuis le point "One"
// le document en BdD a maintenant comme nom "Test One"
// ATTENTION : le document conserve en mémoire sa dernière valeur "Test two".

17.5.3.6.2 Avec plusieurs documents

include_once("FDL/Class.Doc.php");
 
$document1 = new_doc("",8160); // Doc dérive de DbObj
$document1->savePoint("One");
$document1->setValue("first_title","Test one");
$document1->store();  // modification en base doc n°8160 à comme nom "Test One"
 
$document2 = new_doc("",6829); 
$document2->savePoint("two");
$document2->setValue("second_title","Test two");
$document2->store();// modification en base doc n°6829 à comme nom "Test Two"
 
$document3 = new_doc("",9345); 
$document3->setValue("third_title","Test three");
$document3->store();// modification en base doc n°9345 à comme nom "Test three"
 
$document1->rollbackPoint("Two"); // annulation de toutes les requêtes depuis le point "Two"
$document1->commitPoint("One"); // acquittement des requêtes depuis le point "One"
 
// le document1 en BdD a maintenant comme nom "Test One"
// les document2 et document3 n'ont pas été modifiés

Note : Comme indiqué précédemment, ces méthodes ne sont pas liées à l'objet. Le même exemple peut être écrit en utilisant l'action courante ce qui rend moins ambiguë la portée de cette méthode.

include_once("FDL/Class.Doc.php");
 
$action->savePoint("One"); // Premier point
$document1 = new_doc("",8160); // Doc dérive de DbObj
$document1->setValue("first_title","Test one");
$document1->store();  // modification en base doc n°8160 à comme nom "Test One"
 
$action->savePoint("Two");// Second point
$document2 = new_doc("",6829); 
$document2->setValue("second_title","Test two");
$document2->store();// modification en base doc n°6829 à comme nom "Test Two"
 
$document3 = new_doc("",9345); 
$document3->setValue("third_title","Test three");
$document3->store();// modification en base doc n°9345 à comme nom "Test three"
 
$action->rollbackPoint("Two"); // annulation de toutes les requêtes depuis le point "Two"
$action->commitPoint("One"); // acquittement des requêtes depuis le point "One"
 
// le document1 en BD a maintenant comme nom "Test One"
// les document2 et document3 n'ont pas été modifiés

17.5.3.6.3 Confirmation du point d'entrée initial

Néanmoins, il n'est pas obligatoire d'appliquer un commit ou un rollback sur tous les points, mais il est obligatoire de le faire au moins sur le premier.

$document->savePoint("One");
$document->setValue("us_lname","Test one");
$document->store();
 
$document->savePoint("Two");
$document->setValue("us_lname","Test two");
$document->store();
 
$document->savePoint("Three");
$document->setValue("us_lname","Test three");
$document->store();
$document->rollbackPoint("Two"); // on retourne au point Two, le point Three est effacé
 
$document->commitPoint("One"); // on confirme le point One, le point Two est effacé

le document a maintenant comme nom "Test One"

17.5.3.7 Avertissements

17.5.3.7.1 Importation de documents

Ce mécanisme est utilisé lors de l'importation de documents afin d'annuler l'ensemble de l'importation lorsqu'une erreur est détectée. Les hameçons (hooks) utilisés lors de l'importation sont donc exécutés dans une transaction.

17.5.3.7.2 Modification de profil dynamique

Lorqu'un profil dynamique est modifié, tous les documents liés à ce profil ont leurs permissions recalculées.

Chacun calcul de permission est effectuée dans une transaction avec un verrou afin d'interdire la modification simultanée de permission pour un même document.

Si ce calcul de permission est fait dans une transaction déjà mise en place, la table docperm est verrouillée et les locks unitaires des permissions ne sont pas activés afin d'éviter de dépasser le nombre maximum de verrous imposé par postgresql. Dans ce cas précis, toutes les demandes de modification de permission sont mises en attente le temps de la transaction.

17.5.3.7.3 Interblocage

L'utilisation des verrous peut engendrer des problèmes d'interblocage (deadlock). Il est nécessaire d'être familier avec le système de sémaphores afin de minimiser le risque d'interblocage. Si toutefois un interblocage survient, une des deux connexions Postgresql est tuée par le serveur de base de données.

Exemple minimaliste de 2 programmes lancés en parallèle pouvant aboutir à un interblocage.

Programme 1 :

$document1 = new_doc("",8160); 
$document->savePoint("One");
$document->lockPoint(32,"my");
$document->setValue("my_title","Test one"); // ICI POSSIBLE SECTION INTERBLOQUE
$document->store();
$document->lockPoint(45,"my"); // Bloqué par processus 2
$document->commitPoint("One");

Programme 2 :

$document1 = new_doc("",8162);
$document->savePoint("Two");
$document->lockPoint(45,"my");
$document->setValue("my_title","Test two"); // ICI POSSIBLE SECTION INTERBLOQUE
$document->store();
$document->lockPoint(32,"my"); // Bloqué par processus 1
$document->commitPoint("Two");

17.5.3.8 Points de sauvegarde utilisée par le système

Les points de sauvegarde des méthodes ci-dessus sont libérés à la fin de la méthode elle-même.

Méthode Contexte d'utilisation Composition de la clef Composition du verrou
Doc::convert() Conversion d'un document d'une famille vers une autre famille "dcp:convert" + Doc::id Pas de verrou
Doc::revise() Révision de document / Changement d'état "dcp:revise" + Doc::id Pas de verrou
Doc::updateVaultIndex() Enregistrement de nouveaux fichiers dans le document uniqid("dcp:updateVaultIndex") Doc::initid, "UPVI"
DocCtrl::computeDProfil() Enregistrement d'un document ayant un profil dynamique uniqid("dcp:docperm") Doc::initid, "PERM"
DocRel::initRelations() Enregistrement d'un document ayant des relations modifiées uniqid("dcp:initrelation") Doc::initid, "IREL"
UpdateAttribute::beginTransaction() Mise à jour par lot d'attribut de document avec option de transaction "dcp:updateattr" Pas de verrou
ImportDocuments::importDocuments Importation d'une liste de document en mode strict (annulation si une erreur détectée) "dcp:importDocument" Pas de verrou
DetailSearch::isValidPgRegex Vérification des critères de recherche lors de l'enregistrement d'une recherche ou d'un rapport "dcp:isValidPgRegex" Pas de verrou
×
mis à jour