当前位置:首页 > php > 正文内容

laravel中嵌套事务是如何实现的

phpmianshi4个月前 (10-26)php155

关于mysql的事务嵌套

关于mysql 的事务嵌套可以查看这个地址:
https://dev.mysql.com/doc/refman/5.5/en/implicit-commit.html

里面有这么一句话。

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.

大体意思是db不支持事务嵌套,如果你嵌套执行START TRANSACTION时会隐式执行commit

示例

mysql>  BEGIN;
Query OK, 0 rows affected (0.16 sec)

mysql> INSERT INTO T2 VALUES(300);
Query OK, 1 row affected (0.29 sec)

mysql>  BEGIN;
Query OK, 0 rows affected (0.04 sec)

mysql> rollback;
Query OK, 0 rows affected (0.04 sec)

mysql> SELECT * FROM T2;
+------+
| ID   |
+------+
|  300 |
+------+
2 rows in set (0.04 sec)

我们直接rollback上面的语句,但是还是插入了新数据。

laravel之嵌套事务transactions实现

为啥官网不支持,但是 laravel 框架却优雅的实现了事务嵌套,我们来看看它的实现原理。

调用示例:

\DB::beginTransaction();    //主事务
try{
    \DB::beginTransaction(); //子事务
    \DB::insert('insert into T2 set ID=100');
    \DB::rollBack();         //子事务回滚
    \DB::insert('insert into T2 set ID=200');
    \DB::commit();
}catch (\Exception $e) {
    \DB::rollBack();
    echo $e->getMessage();exit;
}


查看执行结果:

mysql> SELECT * FROM T2;
+------+
| ID   |
+------+
|  100 |
+------+
1 row in set (0.05 sec)

说明子事务成功回滚了,下面看下子事务的实现。

代码分析:

laravel/framework/src/Illuminate/Database/Concerns/ManagesTransactions.php 90行

public function beginTransaction()
    {
        $this->createTransaction();

        $this->transactions++;

        $this->fireConnectionEvent('beganTransaction');
    }

每调一次beginTransaction会使t h i s − > t r a n s a c t i o n s 加 1 接 着 看 一 下 this->transactions加1 接着看一下this>transactions加1接着看一下this->createTransaction();的实现

/**
     * Create a transaction within the database.
     *
     * @return void
     */
    protected function createTransaction()
    {
        if ($this->transactions == 0) {
            try {
                $this->getPdo()->beginTransaction();
            } catch (Exception $e) {
                $this->handleBeginTransactionException($e);
            }
        } elseif ($this->transactions >= 1 && $this->queryGrammar->supportsSavepoints()) {
            $this->createSavepoint();
        }
    }

if ($this->transactions == 0) 首先判断是否在事务中。

没有在事务中则执行 $this->getPdo()->beginTransaction() 相当于执行 BEGIN;

在事务中执行 $this->createSavepoint(); 下面是createSavepoint方法的实现。

/**
     * Create a save point within the database.
     *
     * @return void
     */
    protected function createSavepoint()
    {
        $this->getPdo()->exec(
            $this->queryGrammar->compileSavepoint('trans'.($this->transactions + 1))
        );
    }

这里相当于在mysql里执行 SAVEPOINT trans1;

下面看下rollback方法实现:

public function rollBack($toLevel = null)
{
    $toLevel = is_null($toLevel)
                ? $this->transactions - 1
                : $toLevel;

    if ($toLevel < 0 || $toLevel >= $this->transactions) {
        return;
    }
    $this->performRollBack($toLevel);
    $this->transactions = $toLevel;
    $this->fireConnectionEvent('rollingBack');
}

首先rollback会使t h i s − &gt; t r a n s a c t i o n s 减 一 。 然 后 调 用 this-&gt;transactions减一。 然后调用this>transactions减一。然后调用this->performRollBack

protected function performRollBack($toLevel)
{
    if ($toLevel == 0) {
        $this->getPdo()->rollBack();
    } elseif ($this->queryGrammar->supportsSavepoints()) {
        $this->getPdo()->exec(
            $this->queryGrammar->compileSavepointRollBack('trans'.($toLevel + 1))
        );
    }
}

performRollBack方式实际就是在重新设定savepoint值。

下面看下commit的实现:

/**
 * Commit the active database transaction.
 *
 * @return void
 */
public function commit()
{
    if ($this->transactions == 1) {
        $this->getPdo()->commit();
    }

    $this->transactions = max(0, $this->transactions - 1);

    $this->fireConnectionEvent('committed');
}

commit方法,只有在最外层时才会真正的提交。

总结

  1. 基本实现原理是 savepoint

  2. 通过$this->transactions对应的数值设定 不同的savepoint实现不同层次嵌套

  3. 只有在最后一个commit时才会真正提交请求。

SAVEPOINT 使用demo如下:

mysql> CREATE TABLE T2(ID INT);
Query OK, 0 rows affected (0.05 sec)

mysql> select * from T2;
Empty set (0.17 sec)

mysql> BEGIN;
Query OK, 0 rows affected (0.04 sec)

mysql> INSERT INTO T2 VALUES(100);
Query OK, 1 row affected (0.04 sec)

mysql> SAVEPOINT trans1;
Query OK, 0 rows affected (0.04 sec)

mysql> INSERT INTO T2 VALUES(200);
Query OK, 1 row affected (0.04 sec)

mysql> ROLLBACK TO SAVEPOINT trans1;
Query OK, 0 rows affected (0.04 sec)

mysql> RELEASE SAVEPOINT trans1;
Query OK, 0 rows affected (0.03 sec)

mysql> commit;
Query OK, 0 rows affected (0.04 sec)

mysql> SELECT * FROM T2;
+------+
| ID   |
+------+
|  100 |
+------+
1 row in set (0.05 sec)


版权声明:本文由PHP面试资料网发布,如需转载请注明出处。
分享给朋友:

相关文章

php8世界上最好的语言新版本特性

php8世界上最好的语言新版本特性

本文只会讲述新特性的一些关键点,更详细的介绍请认真阅读官方文档。01 Union Types(联合类型)说明一点,从 PHP 7 开始,支持下面这样的语法:function sum(int&...

Windows下nginx+fastcgi+php的并发阻塞问题

同事接到一个需求,需要调用同一个项目的另一个接口,于是CURL调用接口。代码撸完了,本地测试一下 浏览器一直转圈圈直到超时…… 百思不得其解主要是windows+nginx开发环境遇到的问题,本人很少...

PHP内核分析之生命周期五个阶段(四)

一、模块初始化阶段我们先来看一下该阶段的每个函数的作用。1.1、sapi_initialize_request_empty函数// main/SAPI.c SAPI_API vo...

PHP7的垃圾回收机制

PHP7的垃圾回收机制

垃圾回收机制垃圾回收机制是一种动态存储分配方案。它会自动释放程序不再需要的已分配的内存块。 自动回收内存的过程叫垃圾收集。垃圾回收机制可以让程序员不必过分关心程序内存分配,从而将更多的精力投入到业务逻...

PHP中 array_walk array_map array_filter区别

array_walk:array_walk — 使用用户自定义函数对数组中的每个元素做回调处理1. 用户自定义的函数处理每一个元素2. 直接修改原数组,不会创建新的数组3. 可以传递额外的参数更多信息...

php中global与$GLOBAL由引用产生的区别

很多人都认为global和$GLOBALS[]只是写法上面的差别,其实不然。根据官方的解释是$GLOBALS['var'] 是外部的全局变量$var本身。global $var 是外部...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。