Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 701|回复: 0

PHP中实现MySQL嵌套事务的两种解决方案

[复制链接]
  • TA的每日心情
    奋斗
    7 天前
  • 签到天数: 803 天

    [LV.10]以坛为家III

    2053

    主题

    2111

    帖子

    72万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    726482
    发表于 2021-5-18 09:56:21 | 显示全部楼层 |阅读模式

    一、问题起源

    在MySQL的官方文档中有明确的说明不支持嵌套事务:

    1. 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函数调用了B函数,A函数使用了事务,并且是在事务中调用了B函数,B函数也有一个事务,这样就出现了事务嵌套。这时候其实A的事务就意义不大了,为什么呢?上面的文档中就有提到,简单的翻译过来就是:

    1. 当执行一个START TRANSACTION指令时,会隐式的执行一个commit操作。 所以我们就要在系统架构层面来支持事务的嵌套。

     

    所幸的是在一些成熟的ORM框架中都做了对嵌套的支持,比如doctrine或者laravel。接下来我们就一起来看下这两个框架是怎样来实现的。友情提示,这两个框架的函数和变量的命名都比较的直观,虽然看起来很长,但是都是通过命名就能直接得知这个函数或者变量的意思,所以不要一看到那么一大坨就被吓到了 :)

     

    二、doctrine的解决方案

     

    首先来看下在doctrine中创建事务的代码(干掉了不相关的代码):

    [php]  view plain copy
     
    1. /**  
    2. * author http://www.lai18.com  
    3. * date 2015-04-19  
    4. * version 1  
    5. **/   
    6. public function beginTransaction()  
    7.   
    8. {  
    9.   
    10.     ++$this->_transactionNestingLevel;  
    11.   
    12.     if ($this->_transactionNestingLevel == 1) {  
    13.   
    14.         $this->_conn->beginTransaction();  
    15.   
    16.     } else if ($this->_nestTransactionsWithSavepoints) {  
    17.   
    18.         $this->createSavepoint($this->_getNestedTransactionSavePointName());  
    19.   
    20.     }  
    21.   
    22. }   

     

    这个函数的第一行用一个_transactionNestingLevel来标识当前嵌套的级别,如果是1,也就是还没有嵌套,那就用默认的方法执行一下START TRANSACTION就ok了,如果大于1,也就是有嵌套的时候,她会帮我们创建一个savepoint,这个savepoint可以理解为一个事务记录点,当需要回滚时可以只回滚到这个点。然后看下rollBack函数:

     

    [php]  view plain copy
     
    1.     1. /**  
    2. * author http://www.lai18.com  
    3. * date 2015-04-19  
    4. * version 1  
    5. **/   
    6. public function rollBack()  
    7.   
    8. {  
    9.   
    10.     if ($this->_transactionNestingLevel == 0) {  
    11.   
    12.         throw ConnectionException::noActiveTransaction();  
    13.   
    14.     }  
    15.     if ($this->_transactionNestingLevel == 1) {  
    16.   
    17.         $this->_transactionNestingLevel = 0;  
    18.   
    19.         $this->_conn->rollback();  
    20.   
    21.         $this->_isRollbackOnly = false;  
    22.   
    23.     } else if ($this->_nestTransactionsWithSavepoints) {  
    24.   
    25.         $this->rollbackSavepoint($this->_getNestedTransactionSavePointName());  
    26.   
    27.         --$this->_transactionNestingLevel;  
    28.   
    29.     } else {  
    30.   
    31.         $this->_isRollbackOnly = true;  
    32.   
    33.         --$this->_transactionNestingLevel;  
    34.   
    35.     }  
    36.   
    37. }   

     

    可以看到处理的方式也很简单,如果level是1,直接rollback,否则就回滚到前面的savepoint。然后我们继续看下commit函数:

     

    [php]  view plain copy
     
    1.     1. /**  
    2. * author http://www.lai18.com  
    3. * date 2015-04-19  
    4. * version 1  
    5. **/   
    6. public function commit()  
    7.   
    8. {  
    9.   
    10.     if ($this->_transactionNestingLevel == 0) {  
    11.   
    12.         throw ConnectionException::noActiveTransaction();  
    13.   
    14.     }  
    15.   
    16.     if ($this->_isRollbackOnly) {  
    17.   
    18.         throw ConnectionException::commitFailedRollbackOnly();  
    19.   
    20.     }  
    21.     if ($this->_transactionNestingLevel == 1) {  
    22.   
    23.         $this->_conn->commit();  
    24.   
    25.     } else if ($this->_nestTransactionsWithSavepoints) {  
    26.   
    27.         $this->releaseSavepoint($this->_getNestedTransactionSavePointName());  
    28.   
    29.     }  
    30.     --$this->_transactionNestingLevel;  
    31.   
    32. }   

     

    算了,不费口舌解释这段了吧 :)

     

    三、laravel的解决方案laravel的处理方式相对简单粗暴一些,我们先来看下创建事务的操作:

     

    [php]  view plain copy
     
    1.     1.   
    2. /**  
    3. * author http://www.lai18.com  
    4. * date 2015-04-19  
    5. * version 1  
    6. **/   
    7. public function beginTransaction()  
    8.   
    9. {  
    10.   
    11.     ++$this->transactions;  
    12.     if ($this->transactions == 1)  
    13.   
    14.     {  
    15.   
    16.         $this->pdo->beginTransaction();  
    17.   
    18.     }  
    19.   
    20. }   

     

    感觉如何?so easy吧?先判断当前有几个事务,如果是第一个,ok,事务开始,否则就啥都不做,那么为啥是啥都不做呢?继续往下看rollBack的操作:

     

    [php]  view plain copy
     
    1.     1. /**  
    2. * author http://www.lai18.com  
    3. * date 2015-04-19  
    4. * version 1  
    5. **/   
    6. public function rollBack()  
    7.   
    8. {  
    9.   
    10.     if ($this->transactions == 1)  
    11.   
    12.     {  
    13.   
    14.         $this->transactions = 0;  
    15.         $this->pdo->rollBack();  
    16.   
    17.     }  
    18.   
    19.     else  
    20.   
    21.     {  
    22.   
    23.         --$this->transactions;  
    24.   
    25.     }  
    26.   
    27. }   

     

    明白了吧?只有当当前事务只有一个的时候才会真正的rollback,否则只是将计数做减一操作。这也就是为啥刚才说laravel的处理比较简单粗暴一些,在嵌套的内层里面实际上是木有真正的事务的,只有最外层一个整体的事务,虽然简单粗暴,但是也解决了在内层新建一个事务时会造成commit的问题。原理就是这个样子了,为了保持完整起见,把commit的代码也copy过来吧!

     

    [php]  view plain copy
     
    1. public function commit()  
    2.   
    3. {  
    4.   
    5.     if ($this->transactions == 1) $this->pdo->commit();  
    6.     --$this->transactions;  
    7.   
    8. }  
    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-11-24 12:38 , Processed in 1.190495 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表