Mysql
 sql >> Base de données >  >> RDS >> Mysql

Comment détecter que la transaction a déjà commencé ?

Le framework n'a aucun moyen de savoir si vous avez démarré une transaction. Vous pouvez même utiliser $db->query('START TRANSACTION') dont le framework ne saurait rien car il n'analyse pas les instructions SQL que vous exécutez.

Le fait est qu'il incombe à l'application de suivre si vous avez démarré une transaction ou non. Ce n'est pas quelque chose que le framework peut faire.

Je sais que certains frameworks essaient de le faire, et font des choses cocasses comme compter combien de fois vous avez commencé une transaction, ne la résolvant que lorsque vous avez effectué un commit ou un rollback un nombre correspondant de fois. Mais c'est totalement faux car aucune de vos fonctions ne peut savoir si la validation ou la restauration le fera réellement, ou si elles se trouvent dans une autre couche d'imbrication.

(Pouvez-vous dire que j'ai eu cette discussion plusieurs fois ? :-)

Mise à jour 1 : Propulser est une bibliothèque d'accès à la base de données PHP qui prend en charge le concept de "transaction interne" qui ne s'engage pas lorsque vous le lui demandez. Commencer une transaction ne fait qu'incrémenter un compteur, et commit/rollback décrémente le compteur. Vous trouverez ci-dessous un extrait d'un fil de liste de diffusion où je décris quelques scénarios où cela échoue.

Mise à jour 2 : Doctrine DBAL possède également cette fonctionnalité. Ils l'appellent Transaction Nesting.

Qu'on le veuille ou non, les transactions sont "globales" et n'obéissent pas à l'encapsulation orientée objet.

Scénario de problème 1

J'appelle commit() , mes modifications sont-elles validées ? Si je cours à l'intérieur d'une "transaction interne", ils ne le sont pas. Le code qui gère la transaction externe pourrait choisir de revenir en arrière, et mes modifications seraient rejetées à mon insu ou sous mon contrôle.

Par exemple :

  1. Modèle A :commencer la transaction
  2. Modèle A :effectuez certaines modifications
  3. Modèle B :commencer la transaction (sans opération silencieuse)
  4. Modèle B :effectuez certaines modifications
  5. Modèle B :validation (sans opération silencieuse)
  6. Modèle A :rollback (ignore les modifications du modèle A et du modèle B)
  7. Modèle B :WTF ! ? Qu'est-il arrivé à mes modifications ?

Scénario de problème n° 2

Une transaction interne est annulée, elle pourrait ignorer les modifications légitimes apportées par une transaction externe. Lorsque le contrôle est rendu au code externe, il pense que sa transaction est toujours active et disponible pour être validée. Avec votre patch, ils pourraient appeler commit() , et puisque le transDepth est maintenant 0, il définirait silencieusement $transDepth à -1 et renvoie true, après n'avoir rien commis.

Scénario de problème #3

Si j'appelle commit() ou rollback() lorsqu'il n'y a pas de transaction active, il définit le $transDepth à -1. Le prochain beginTransaction() incrémente le niveau à 0, ce qui signifie que la transaction ne peut être ni annulée ni validée. Appels ultérieurs à commit() décrémentera simplement la transaction à -1 ou plus, et vous ne pourrez jamais valider jusqu'à ce que vous fassiez un autre beginTransaction() superflu pour incrémenter à nouveau le niveau.

Fondamentalement, essayer de gérer les transactions dans la logique de l'application sans permettre à la base de données de faire la comptabilité est une idée vouée à l'échec. Si vous avez besoin que deux modèles utilisent le contrôle de transaction explicite dans une demande d'application, vous devez ouvrir deux connexions de base de données, une pour chaque modèle. Ensuite, chaque modèle peut avoir sa propre transaction active, qui peut être validée ou annulée indépendamment l'une de l'autre.