关于 MySQL 的嵌套事务

MySQL事务

事务


事务的概念就不介绍了, 它的目的是解决ACID问题.

  • MySQL中的事务必须是InnoDB、Berkeley DB引擎,myisam不支持, 新出了一个memory, 貌似也是不支持的, 大家可以查查。
  • MySQL默认是autocommit=1,也就是说默认是立即提交,如果想开启事务,先设置autocommit=0,然后用START TRANSACTION、 COMMIT、 ROLLBACK来使用具体的事务。

    你可能会有一个疑问: 查询会不会开事务?

    根据上面两点可以得出, 如果你在INNODB事务引擎下, 并且autocommit=1 (默认值), 答案是会, 否则不会. 其他引擎不支持事务, 这个问题也不存在.

    这时候你可能又有另外一个疑问: 我只是执行单个查询语句, 为什么要开事务?

  • 如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持SQL执行期间的读一致性;
  • 如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询SQL必须保证整体的读一致性,否则,在前条SQL查询之后,后条SQL查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持。

    简而言之, 因为你需要ACID中的CI: consistency && isolated

    嵌套事务

    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.

    Mysql是不支持嵌套事务的,开启了一个事务的情况下,再开启一个事务,会隐式的提交上一个事务. 所以我们就要在系统架构层面来支持事务的嵌套, 常见的做法就是SAVEPOINT.

    savePoint机制

==测试数据表结构==

id stock
1 109
START TRANSACTION;
SAVEPOINT a;
UPDATE `user` SET stock = 1 WHERE id = 1;
SAVEPOINT b;
UPDATE `user` SET stock = 2 WHERE id = 1;
ROLLBACK TO b;
commit;

执行的代码,会发有数据表中id=1的行stock值被修改为1。

解读一下代码

  1. 开启事物
  2. 建立保存点a
  3. 更新数据stock为1
  4. 建立保存点b(此时id=1的数据stock=1)
  5. 更新stock为2
  6. 回滚至savepoint b
  7. 提交事物

粗读这段代码,即可以知道,savepoint是可以在事务进行中建立保存点,类似游戏中的存档一样,但是有个不一样的地方就是savepoint的持有者是事务,当事务commit或者是完全rollback之后,savepoint就会释放。

THINKPHP

PHP框架THINKPHP5的嵌套事务处理策略: 如果开启了supportSavepoint, 则利用SAVEPOINT来等价子事务, 否则啥也不干, MySQL驱动下默认开启

这产生了一个很重要的结论: 子事务的回滚不会导致事务回滚, 只有第一个事务的回滚才是真正的ROLLBACK

==TP5.1关于嵌套事务的实现源码截取==

    /**
     * 启动事务
     * @access public
     * @return void
     * @throws \PDOException
     * @throws \Exception
     */
    public function startTrans()
    {
        $this->initConnect(true);
        if (!$this->linkID) {
            return false;
        }

        ++$this->transTimes;

        try {
            if (1 == $this->transTimes) {
                $this->linkID->beginTransaction();
            } elseif ($this->transTimes > 1 && $this->supportSavepoint()) {
                $this->linkID->exec(
                    $this->parseSavepoint('trans' . $this->transTimes)
                );
            }
        } catch (\Exception $e) {
            if ($this->isBreak($e)) {
                --$this->transTimes;
                return $this->close()->startTrans();
            }
            throw $e;
        }
    }

    /**
     * 事务回滚
     * @access public
     * @return void
     * @throws PDOException
     */
    public function rollback()
    {
        $this->initConnect(true);

        if (1 == $this->transTimes) {
            $this->linkID->rollBack();
        } elseif ($this->transTimes > 1 && $this->supportSavepoint()) {
            $this->linkID->exec(
                $this->parseSavepointRollBack('trans' . $this->transTimes)
            );
        }

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

    /**
     * 用于非自动提交状态下面的查询提交
     * @access public
     * @return void
     * @throws PDOException
     */
    public function commit()
    {
        $this->initConnect(true);

        if (1 == $this->transTimes) {
            $this->linkID->commit();
        }

        --$this->transTimes;
    }

    /**
     * 是否支持事务嵌套
     * @return bool
     */
    protected function supportSavepoint()
    {
        return false;
    }

    /**
     * 生成定义保存点的SQL
     * @access protected
     * @param  $name
     * @return string
     */
    protected function parseSavepoint($name)
    {
        return 'SAVEPOINT ' . $name;
    }

==从TP的实现源码上面可以知道,TP框架在出现嵌套事务时,处理方法就是采用savepoint的策略。startTrans 与 rollback方法都不是直接执行mysql的rollback。他执行分两步==

  • 判断是不是第一次开启事务,如果是的话就开启事务,否则这是单纯的执行savepoint方法。
  • 回滚rollback的时候也是同理,只有当时最外层的事务执行rollback时,他才会执行mysql的rollback,否则都是rollback to savepoint。
本作品采用《CC 协议》,转载必须注明作者和本文链接
大人中
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 2

棒,这两天也在看laravel对多次使用DB::beginTransaction();的处理

4年前 评论

好东西 mark

4年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!