一、問題起源
在mysql的官方文檔中有明確的說明不支持嵌套事務(wù):
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.
但是在我們開發(fā)一個復(fù)雜的系統(tǒng)時難免會無意中在事務(wù)中嵌套了事務(wù),比如A函數(shù)調(diào)用了B函數(shù),A函數(shù)使用了事務(wù),并且是在事務(wù)中調(diào)用了B函數(shù),B函數(shù)也有一個事務(wù),這樣就出現(xiàn)了事務(wù)嵌套。這時候其實A的事務(wù)就意義不大了,為什么呢?上面的文檔中就有提到,簡單的翻譯過來就是:
當(dāng)執(zhí)行一個START TRANSACTION指令時,會隱式的執(zhí)行一個commit操作。
所以我們就要在系統(tǒng)架構(gòu)層面來支持事務(wù)的嵌套。所幸的是在一些成熟的ORM框架中都做了對嵌套的支持,比如doctrine或者laravel。接下來我們就一起來看下這兩個框架是怎樣來實現(xiàn)的。
友情提示,這兩個框架的函數(shù)和變量的命名都比較的直觀,雖然看起來很長,但是都是通過命名就能直接得知這個函數(shù)或者變量的意思,所以不要一看到那么一大坨就被嚇到了 ??
二、doctrine的解決方案
首先來看下在doctrine中創(chuàng)建事務(wù)的代碼(干掉了不相關(guān)的代碼):
publicfunctionbeginTransaction(){ ????++$this->_transactionNestingLevel;???? ????if?($this->_transactionNestingLevel?==?1)?{???????? ????$this->_conn->beginTransaction(); ????}?elseif?($this->_nestTransactionsWithSavepoints)?{???????? ????$this->createSavepoint($this->_getNestedTransactionSavePointName()); ????} }
這個函數(shù)的第一行用一個_transactionNestingLevel來標(biāo)識當(dāng)前嵌套的級別,如果是1,也就是還沒有嵌套,那就用默認(rèn)的方法執(zhí)行一下START TRANSACTION就ok了,如果大于1,也就是有嵌套的時候,她會幫我們創(chuàng)建一個savepoint,這個savepoint可以理解為一個事務(wù)記錄點,當(dāng)需要回滾時可以只回滾到這個點。
然后看下rollBack函數(shù):
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; ????} }
可以看到處理的方式也很簡單,如果level是1,直接rollback,否則就回滾到前面的savepoint。
然后我們繼續(xù)看下commit函數(shù):
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; }
算了,不費口舌解釋這段了吧 ??
三、laravel的解決方案
laravel的處理方式相對簡單粗暴一些,我們先來看下創(chuàng)建事務(wù)的操作:
publicfunctionbeginTransaction(){ ????++$this->transactions;????if?($this->transactions?==?1) ????{????????$this->pdo->beginTransaction(); ????} }
感覺如何?so easy吧?先判斷當(dāng)前有幾個事務(wù),如果是第一個,ok,事務(wù)開始,否則就啥都不做,那么為啥是啥都不做呢?繼續(xù)往下看rollBack的操作:
publicfunctionrollBack(){????if?($this->transactions?==?1) ????{????????$this->transactions?=?0;????????$this->pdo->rollBack(); ????}????else ????{ ????????--$this->transactions; ????} }
明白了吧?只有當(dāng)當(dāng)前事務(wù)只有一個的時候才會真正的rollback,否則只是將計數(shù)做減一操作。這也就是為啥剛才說laravel的處理比較簡單粗暴一些,在嵌套的內(nèi)層里面實際上是木有真正的事務(wù)的,只有最外層一個整體的事務(wù),雖然簡單粗暴,但是也解決了在內(nèi)層新建一個事務(wù)時會造成commit的問題。原理就是這個樣子了,為了保持完整起見,把commit的代碼也copy過來吧!
publicfunctioncommit(){???? if?($this->transactions?==?1)?$this->pdo->commit(); ????--$this->transactions; }
以上就是MySQL的嵌套事務(wù)實現(xiàn)的內(nèi)容,更多相關(guān)內(nèi)容請關(guān)注PHP中文網(wǎng)(www.php.cn)!