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

一、问题起源

在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 plaincopy

  1. /**
  2. * author http://www.lai18.com
  3. * date 2015-04-19
  4. * version 1
  5. **/
  6. public function beginTransaction()
  7. {
  8. ++$this->_transactionNestingLevel;
  9. if ($this->_transactionNestingLevel == 1) {
  10. $this->_conn->beginTransaction();
  11. } else if ($this->_nestTransactionsWithSavepoints) {
  12. $this->createSavepoint($this->_getNestedTransactionSavePointName());
  13. }
  14. }

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

[php] view plaincopy

  1. 1. /**
  2. * author http://www.lai18.com
  3. * date 2015-04-19
  4. * version 1
  5. **/
  6. public function rollBack()
  7. {
  8. if ($this->_transactionNestingLevel == 0) {
  9. throw ConnectionException::noActiveTransaction();
  10. }
  11. if ($this->_transactionNestingLevel == 1) {
  12. $this->_transactionNestingLevel = 0;
  13. $this->_conn->rollback();
  14. $this->_isRollbackOnly = false;
  15. } else if ($this->_nestTransactionsWithSavepoints) {
  16. $this->rollbackSavepoint($this->_getNestedTransactionSavePointName());
  17. --$this->_transactionNestingLevel;
  18. } else {
  19. $this->_isRollbackOnly = true;
  20. --$this->_transactionNestingLevel;
  21. }
  22. }

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

[php] view plaincopy

  1. 1. /**
  2. * author http://www.lai18.com
  3. * date 2015-04-19
  4. * version 1
  5. **/
  6. public function commit()
  7. {
  8. if ($this->_transactionNestingLevel == 0) {
  9. throw ConnectionException::noActiveTransaction();
  10. }
  11. if ($this->_isRollbackOnly) {
  12. throw ConnectionException::commitFailedRollbackOnly();
  13. }
  14. if ($this->_transactionNestingLevel == 1) {
  15. $this->_conn->commit();
  16. } else if ($this->_nestTransactionsWithSavepoints) {
  17. $this->releaseSavepoint($this->_getNestedTransactionSavePointName());
  18. }
  19. --$this->_transactionNestingLevel;
  20. }

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

 

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

 

[php] view plaincopy

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

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

[php] view plaincopy

  1. 1. /**
  2. * author http://www.lai18.com
  3. * date 2015-04-19
  4. * version 1
  5. **/
  6. public function rollBack()
  7. {
  8. if ($this->transactions == 1)
  9. {
  10. $this->transactions = 0;
  11. $this->pdo->rollBack();
  12. }
  13. else
  14. {
  15. --$this->transactions;
  16. }
  17. }

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

[php] view plaincopy

  1. public function commit()
  2. {
  3. if ($this->transactions == 1) $this->pdo->commit();
  4. --$this->transactions;
  5. }
时间: 2024-08-25 06:44:39

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

sql server中批量插入与更新两种解决方案分享

若只是需要大批量插入数据使用bcp是最好的,若同时需要插入.删除.更新建议使用SqlDataAdapter我测试过有很高的效率,一般情况下这两种就满足需求了 bcp方式 复制代码 代码如下: /// <summary> /// 大批量插入数据(2000每批次) /// 已采用整体事物控制 /// </summary> /// <param name="connString">数据库链接字符串</param> /// <param n

sql server中批量插入与更新两种解决方案分享(存储过程)

转自http://www.shangxueba.com/jingyan/1940447.html 1.游标方式 复制代码代码如下: DECLARE @Data NVARCHAR(max) SET @Data='1,tanw,2,keenboy' --Id,Name DECLARE @dataItem NVARCHAR(100) DECLARE data_cursor CURSOR FOR (SELECT * FROM split(@Data,';')) OPEN data_cursor FETC

C++连接mysql数据库的两种方法

现在正做一个接口,通过不同的连接字符串操作不同的数据库.要用到mysql数据库,以前没用过这个数据库,用access和sql server比较多.通过网上的一些资料和自己的摸索,大致清楚了C++连接mysql的方法.可以通过2种方法实现. 第一种方法是利用ADO连接, 第二种方法是利用mysql自己的api函数进行连接. 第一种方法可以实现我当前的需求,通过连接不同的字符串来连接不同的数据库.暂时只连接了mysql,sqlserver,oracle,access.对于access,因为它创建表的

MyBatis中主键回填的两种实现方式

主键回填其实是一个非常常见的需求,特别是在数据添加的过程中,我们经常需要添加完数据之后,需要获取刚刚添加的数据 id,无论是 Jdbc 还是各种各样的数据库框架都对此提供了相关的支持,本文我就来和和大家分享下数据库主键回填在 MyBatis 中的两种实现思路. 原生写法 框架来源于我们学过的基础知识,主键回填实际上是一个在 JDBC 中就被支持的写法,有的小伙伴可能不知道这一点,因此这里我先来说说在 JDBC 中如何实现主键回填. JDBC 中实现主键回填其实非常容易,主要是在构造 Prepar

21_django配置使用mysql数据库的两种方式

目录 配置django项目使用mysql数据库的两种方式 1. 直接在settings.py 文件中添加数据库配置信息 2. 将数据库配置信息存到一个文件中,在settings.py文件中将其引入.(推荐) 安装mysql驱动 1. 使用mysqlclient *推荐 2. 使用pymysql django2.2以上版本默认不支持使用了 配置django项目使用mysql数据库的两种方式 1. 直接在settings.py 文件中添加数据库配置信息 # 配置数据库的第一种方式 DATABASES

HTML中设置背景图的两种方式

HTML中设置背景图的两种方式 1.background    background:url(images/search.png) no-repeat top; 2.background-image    background-image:url(images/search.png):    background-repeat:no-repeat;

action中请求参数获取的两种方式

action中请求参数获取的两种方式 1.属性驱动? a.直接在 action 类中提供与请求参数匹配属性,提供 get/set 方法? b.在 action 类中创始一个 javaBean,对其提供 get/set ,在请求时页面上要进行修改,? 例如 user.username user.password ,要使用 ognl 表达式? 以上两种方式的优缺点:? 第一种比较简单,在实际操作我们需要将 action 的属性在赋值给模型(javaBean)去操作? 第二种:不需要在直接将值给 ja

Ajax中的get和post两种请求方式的异同

Ajax中我们经常用到get和post请求.那么什么时候用get请求,什么时候用post方式请求呢? 在做回答前我们首先要了解get和post的区别.   1. get是把参数数据队列加到提交表单的ACTION属性所指的URL中,值和表单内各个字段一一对应,在URL中可以看到.post是通过HTTP post机制,将表单内各个字段与其内容放置在HTML HEADER内一起传送到ACTION属性所指的URL地址.用户看不到这个过程.   2. 对于get方式,服务器端用Request.QueryS

strus2中获取表单数据 两种方式 属性驱动 和模型驱动

strus2中获取表单数据 两种方式 属性驱动 和模型驱动 属性驱动 /** * 当前请求的action在栈顶,ss是栈顶的元素,所以可以利用setValue方法赋值* 如果一个属性在对象栈,在页面上可以根据name属性进行回显*/ /** * 属性驱动实现的条件:* 1.当前请求的action在栈顶,所以action中的属性就暴漏出来了* 2.获取页面上表单的元素,整合成一个map * 3.调用setValue方法赋值*/ 1 package cn.itcast.struts2.sh; 2 3