MySQL的嵌套事務(wù)實現(xiàn)

一、問題起源

在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)!

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點贊6 分享