1. 問題の原因
MySQL の公式ドキュメントでは、ネストされたトランザクションはサポートされていないと明確に述べられています:
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.
しかし、複雑なシステムを開発する場合、トランザクションが誤ってトランザクション内にネストされることは避けられません。関数 A が呼び出され、関数 A がトランザクションを使用し、関数 B もトランザクション内で呼び出されるため、トランザクションのネストが発生します。この時点では、A の事柄は実際にはほとんど重要ではありません。なぜですか。上記のドキュメントで説明されています。簡単に翻訳すると、次のようになります。
START TRANSACTION 命令が実行されると、コミット操作が暗黙的に実行されます。
そのため、システム アーキテクチャ レベルでトランザクションのネストをサポートする必要があります。幸いなことに、doctrine や laravel など、一部の成熟した ORM フレームワークではネストがサポートされています。次に、これら 2 つのフレームワークがどのように実装されているかを見てみましょう。
これら 2 つのフレームワークにおける関数と変数の名前付けは比較的直感的ですが、Datuo では名前付けを通じて関数や変数の意味を直接知ることができるので、最初は混乱しないように注意してください。怖かったです:)
2番目に、doctrineの解決策
まず、doctrineでトランザクションを作成するコードを見てみましょう(無関係なコードを削除します):
publicfunctionbeginTransaction(){ ++$this->_transactionNestingLevel; if ($this->_transactionNestingLevel == 1) { $this->_conn->beginTransaction(); } elseif ($this->_nestTransactionsWithSavepoints) { $this->createSavepoint($this->_getNestedTransactionSavePointName()); } }
この関数の最初の行は、_transactionNestingLevelを使用し、現在のネストレベルを識別します。それが 1 の場合、つまりネストがまだない場合は、デフォルトのメソッドを使用して START TRANSACTION を実行します。1 より大きい場合、つまりネストがある場合は、彼女が作成を手伝ってくれます。セーブポイント。このセーブポイントは、ロールバックが必要な場合、このポイントまでのみロールバックできます。
次に、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; } }
レベルが 1 の場合は直接ロールバックし、それ以外の場合は前のセーブポイントにロールバックする処理方法も非常に単純であることがわかります。
それではコミット関数を見ていきましょう:
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; }
忘れてください、簡単に説明しましょう:)
3. Laravel の解決策
Laravel の処理方法は比較的単純で大雑把なので、最初に見てみましょう。トランザクション
publicfunctionbeginTransaction(){ ++$this->transactions; if ($this->transactions == 1) { $this->pdo->beginTransaction(); } }
を作成するのはどんな感じですか?とても簡単ですよね?まず、現在存在するトランザクションの数を確認します。それが最初のトランザクションである場合、トランザクションは開始されます。それ以外の場合、何も行われないのはなぜでしょうか。引き続き、rollBack の操作を見てみましょう:
publicfunctionrollBack(){ if ($this->transactions == 1) { $this->transactions = 0; $this->pdo->rollBack(); } else { --$this->transactions; } }
理解できましたか?現在のトランザクションが 1 つだけである場合にのみ、実際にロールバックされます。それ以外の場合は、カウントが 1 つだけデクリメントされます。これが、Laravel の処理が比較的単純で粗雑であると述べた理由です。実際には、ネストされた内側の層には実際のトランザクションは存在しません。最も外側の層には全体的なトランザクションがあるだけです。しかし、これによって問題も解決されます。内部層が新しいトランザクションを作成すると、コミットの問題が発生します。原則は次のとおりです。完全を期すために、コミット コードもコピーしてください。
publicfunctioncommit(){ if ($this->transactions == 1) $this->pdo->commit(); --$this->transactions; }
上記は MySQL のネストされたトランザクションの実装の内容です。さらに関連する内容については、PHP 中国語 Web サイト (www.php.cn) に注目してください。