最近被人问到,MySql 是否可以部分事务回滚。我义正言辞的书不能,最后被教育了。写了这么多代码都不知道 mysql 的保存点。今天有空记录下研究的 savepoint 。共勉!
mysql 事务有一个保存点功能,可以回退到固定点。点的设置没有数量限制,如果执行 COMMIT 或未 ROLLBACK 命名保存点,会删除当前事务中定义的保存点。

SAVEPOINT 两个主要功能

保存点设置

SAVEPOINT identifier(保存点名称)

回退到某个保存点

ROLLBACK TO identifier(保存点名称)

SAVEPOINT 在框架中应用

ThinkPHP

  1. /**
  2. * 启动事务
  3. * @access public
  4. * @return void
  5. * @throws \PDOException
  6. * @throws \Exception
  7. */
  8. public function startTrans()
  9. {
  10. //初始化数据库连接
  11. $this->initConnect(true);
  12. //当前连接ID
  13. if (!$this->linkID) {
  14. return false;
  15. }
  16. //活跃的事务指令数,默认值为0
  17. ++$this->transTimes;
  18. try {
  19. if (1 == $this->transTimes) {
  20. //启动一个事务
  21. $this->linkID->beginTransaction();
  22. } elseif ($this->transTimes > 1 && $this->supportSavepoint()) {
  23. //开启多次事务 $this->supportSavepoint() mysql 返回 true
  24. //数据库连接执行一条SQL语句并返回受影响的行数
  25. $this->linkID->exec(
  26. // 生成定义保存点的SQL
  27. $this->parseSavepoint('trans' . $this->transTimes)
  28. );
  29. }
  30. } catch (\Exception $e) {
  31. //如果断线重新开始事务
  32. if ($this->isBreak($e)) {
  33. --$this->transTimes;
  34. //关闭本次连接 递归调用自己
  35. return $this->close()->startTrans();
  36. }
  37. throw $e;
  38. }
  39. }
 /**
     * 事务回滚
     * @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()) {
 //数据库连接执行一条SQL语句并返回受影响的行数
 $this->linkID->exec(
 //生成回滚到保存点的SQL
 $this->parseSavepointRollBack('trans' . $this->transTimes)
            );
        }

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

可以看到第一次调用后再调用 startTrans 都会增加一个自定义保存节点。rollback 方法方法也是一层层返回到上次节点,直到最后一次全部回滚。commit 与 rollback 同理

Laravel

 /**
     * 启动事务
     * Start a new database transaction.
     *
     * @return void
     *
     * @throws \Exception
     */
 public function beginTransaction()
 {
 //创建事务
 $this->createTransaction();
 //活跃的事务指令数,默认值为0
 $this->transactions++;

 $this->fireConnectionEvent('beganTransaction');
    }
 /**
     * Create a transaction within the database.
     *
     * @return void
     */
 protected function createTransaction()
 {
 if ($this->transactions == 0) {
 try {
 //获取当前PDO连接。并开启事务
 $this->getPdo()->beginTransaction();
            } catch (Exception $e) {
 //开启事务异常重新连接数据库并且开启事务
 $this->handleBeginTransactionException($e);
            }
        } elseif ($this->transactions >= 1 && $this->queryGrammar->supportsSavepoints()) {
 //开启多次事务   $this->queryGrammar->supportsSavepoints() mysql 返回 true 
 $this->createSavepoint();
        }
    }

 /**
     * Create a save point within the database.
     *
     * @return void
     */
 protected function createSavepoint()
 {
 //数据库连接执行一条SQL语句并返回受影响的行数
 $this->getPdo()->exec(
 // 生成定义保存点的SQL
 $this->queryGrammar->compileSavepoint('trans'.($this->transactions + 1))
        );
    }
 /**
     * 回滚
     * Rollback the active database transaction.
     *
     * @param  int|null  $toLevel
     * @return void
     *
     * @throws \Exception
     */
 public function rollBack($toLevel = null)
 {
 //可以传输想要回退的等级,在尝试回滚到之前,该给定事务级别是否有效
 $toLevel = is_null($toLevel)
                    ? $this->transactions - 1
                    : $toLevel;

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


 try {
 $this->performRollBack($toLevel);
        } catch (Exception $e) {
 $this->handleRollBackException($e);
        }

 $this->transactions = $toLevel;
 //触发事件
 $this->fireConnectionEvent('rollingBack');
    }

 /**
     * Perform a rollback within the database.
     *
     * @param  int  $toLevel
     * @return void
     */
 protected function performRollBack($toLevel)
 {
 if ($toLevel == 0) {
 $this->getPdo()->rollBack();
        } elseif ($this->queryGrammar->supportsSavepoints()) {
 $this->getPdo()->exec(
 $this->queryGrammar->compileSavepointRollBack('trans'.($toLevel + 1))
            );
        }
    }

 /**
     * 提交
     * 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');
    }

总结:

  • mysql 支持部分事务回滚。
  • 现在框架都实现了嵌套事务。 Laravel 回滚可以指定回滚的级别。其他大同小异。