1. Origine du problème
La documentation officielle de MySQL indique clairement que les transactions imbriquées ne sont pas prises en charge :
Transactions cannot be nested. This is a consequence of the implicit commit performed for any current transaction when you issue a START TRANSACTION statement or one of its synonyms.
Mais lorsque nous développons un système complexe, il est inévitable que nous le fassions Si vous le faites accidentellement, il y a une transaction imbriquée dans la transaction. Par exemple, la fonction A appelle la fonction B, la fonction A utilise une transaction et la fonction B est appelée dans la transaction. La fonction B a également une transaction, donc l'imbrication des transactions se produit. À l’heure actuelle, les affaires de A n’ont en réalité que peu d’importance. Pourquoi ? C'est mentionné dans le document ci-dessus. Une traduction simple est :
Lorsqu'une instruction START TRANSACTION est exécutée, une opération de validation sera implicitement exécutée.
Nous devons donc prendre en charge l'imbrication des transactions au niveau de l'architecture du système. Heureusement, certains frameworks ORM matures prennent en charge l'imbrication, comme Doctrine ou Laravel. Voyons ensuite comment ces deux frameworks sont implémentés.
Rappel amical, la dénomination des fonctions et des variables dans ces deux frameworks est relativement intuitive Bien que cela semble très long, vous pouvez connaître directement la signification de la fonction ou de la variable grâce à la dénomination, alors n'ai-je pas eu peur. quand j'ai vu un si gros gâchis :)
2. La solution de Doctrine
Tout d'abord, jetons un coup d'œil au code pour créer des transactions dans Doctrine (code non pertinent supprimé) :
publicfunctionbeginTransaction(){ ++$this->_transactionNestingLevel; if ($this->_transactionNestingLevel == 1) { $this->_conn->beginTransaction(); } elseif ($this->_nestTransactionsWithSavepoints) { $this->createSavepoint($this->_getNestedTransactionSavePointName()); } }
La première ligne de cette fonction utilise un _transactionNestingLevel pour identifier le niveau d'imbrication actuel. S'il est 1, c'est-à-dire qu'il n'y a pas encore d'imbrication, alors utilisez la méthode par défaut pour exécuter START TRANSACTION et tout ira bien. il est supérieur à 1, c'est-à-dire qu'en cas d'imbrication, elle nous aidera à créer un point de sauvegarde. Ce point de sauvegarde peut être compris comme un point d'enregistrement de transaction. Lorsqu'une restauration est nécessaire, vous ne pouvez revenir qu'à ce point.
Regardez ensuite la fonction rollBack :
publicfunctionrollBack(){ if ($this->_transactionNestingLevel == 0) { throw ConnectionException::noActiveTransaction(); } if ($this->_transactionNestingLevel == 1) { $this->_transactionNestingLevel = 0; $this->_conn->rollback(); $this->_isRollbackOnly = false; } elseif ($this->_nestTransactionsWithSavepoints) { $this->rollbackSavepoint($this->_getNestedTransactionSavePointName()); --$this->_transactionNestingLevel; } else { $this->_isRollbackOnly = true; --$this->_transactionNestingLevel; } }
Vous pouvez voir que la méthode de traitement est également très simple Si le niveau est 1, rollback directement, sinon rollback au point de sauvegarde précédent.
Ensuite, continuons à regarder la fonction commit :
publicfunctioncommit(){ if ($this->_transactionNestingLevel == 0) { throw ConnectionException::noActiveTransaction(); } if ($this->_isRollbackOnly) { throw ConnectionException::commitFailedRollbackOnly(); } if ($this->_transactionNestingLevel == 1) { $this->_conn->commit(); } elseif ($this->_nestTransactionsWithSavepoints) { $this->releaseSavepoint($this->_getNestedTransactionSavePointName()); } --$this->_transactionNestingLevel; }
Oubliez ça, expliquons cela sans tracas :)
La solution de Laravel
La méthode de traitement de Laravel est relativement simple et grossière. Regardons d'abord l'opération de création d'une transaction :
publicfunctionbeginTransaction(){ ++$this->transactions; if ($this->transactions == 1) { $this->pdo->beginTransaction(); } }
Comment vous sentez-vous ? Tellement facile, non ? Déterminez d’abord combien de transactions il y a actuellement. Si c’est la première, ok, la transaction démarre. Sinon, rien n’est fait. Alors pourquoi rien n’est fait ? Continuez à regarder le fonctionnement du rollBack :
publicfunctionrollBack(){ if ($this->transactions == 1) { $this->transactions = 0; $this->pdo->rollBack(); } else { --$this->transactions; } }
Vous comprenez ? Ce n'est que lorsqu'il n'y a qu'une seule transaction en cours qu'elle sera véritablement annulée, sinon le nombre sera simplement décrémenté d'un. C'est pourquoi je viens de dire que le traitement de Laravel est relativement simple et grossier. Il n'y a en fait aucune transaction réelle dans la couche interne imbriquée. Bien qu'elle soit simple et grossière, elle résout également le problème. of Lorsque la couche interne crée une nouvelle transaction, cela entraînera des problèmes de validation. Le principe est le suivant. Par souci d'exhaustivité, copiez également le code de commit !
publicfunctioncommit(){ if ($this->transactions == 1) $this->pdo->commit(); --$this->transactions; }
Ce qui précède est le contenu de l'implémentation des transactions imbriquées de MySQL. Pour plus de contenu connexe, veuillez faire attention au site Web PHP chinois (www.php.cn) !