最近被人问到,MySql 是否可以部分事务回滚。我义正言辞的书不能,最后被教育了。写了这么多代码都不知道 mysql 的保存点。今天有空记录下研究的 savepoint 。共勉!
mysql 事务有一个保存点功能,可以回退到固定点。点的设置没有数量限制,如果执行 COMMIT 或未 ROLLBACK 命名保存点,会删除当前事务中定义的保存点。
SAVEPOINT 两个主要功能
保存点设置
SAVEPOINT identifier(保存点名称)
回退到某个保存点
ROLLBACK TO identifier(保存点名称)
SAVEPOINT 在框架中应用
ThinkPHP
/*** 启动事务* @access public* @return void* @throws \PDOException* @throws \Exception*/public function startTrans(){//初始化数据库连接$this->initConnect(true);//当前连接IDif (!$this->linkID) {return false;}//活跃的事务指令数,默认值为0++$this->transTimes;try {if (1 == $this->transTimes) {//启动一个事务$this->linkID->beginTransaction();} elseif ($this->transTimes > 1 && $this->supportSavepoint()) {//开启多次事务 $this->supportSavepoint() mysql 返回 true//数据库连接执行一条SQL语句并返回受影响的行数$this->linkID->exec(// 生成定义保存点的SQL$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()) {
//数据库连接执行一条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 回滚可以指定回滚的级别。其他大同小异。
