标签:synonym nod 通过 rollback 使用 any 回滚 @param 语句
在 MySQL 的官方文档中有明确的说明不支持嵌套事务:
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.
翻译:
当执行一个
START TRANSACTION
指令时,会隐式的执行一个commit
操作。 所以我们就要在系统架构层面来支持事务的嵌套。
所幸的是在一些成熟的ORM
框架中都做了对嵌套的支持,比如 ThinkPHP 和 Laravel等。
如果多层嵌套,transTimes
会进行标记。如果嵌套,只有最外层的事务是生效的。这种事务嵌套处理方式跟laravel
是一模一样的。
文件位置:vendor/topthink/think-orm/src/db/PDOConnection.php
/**
* 启动事务
* @access public
* @return void
* @throws \PDOException
* @throws \Exception
*/
public function startTrans(): void
{
try {
$this->initConnect(true);
++$this->transTimes;
if (1 == $this->transTimes) {
$this->linkID->beginTransaction();
} elseif ($this->transTimes > 1 && $this->supportSavepoint()) {
$this->linkID->exec(
$this->parseSavepoint(‘trans‘ . $this->transTimes)
);
}
$this->reConnectTimes = 0;
} catch (\Exception $e) {
if ($this->reConnectTimes < 4 && $this->isBreak($e)) {
--$this->transTimes;
++$this->reConnectTimes;
$this->close()->startTrans();
} else {
throw $e;
}
}
}
/**
* 用于非自动提交状态下面的查询提交
* @access public
* @return void
* @throws PDOException
*/
public function commit(): void
{
$this->initConnect(true);
if (1 == $this->transTimes) {
$this->linkID->commit();
}
--$this->transTimes;
}
/**
* 事务回滚
* @access public
* @return void
* @throws PDOException
*/
public function rollback(): void
{
$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);
}
文件位置:/laravel/framework/blob/8.x/src/Illuminate/Database/DatabaseTransactionsManager.php
/**
* Start a new database transaction.
*
* @param string $connection
* @param int $level
* @return void
*/
public function begin($connection, $level)
{
$this->transactions->push(
new DatabaseTransactionRecord($connection, $level)
);
}
/**
* Rollback the active database transaction.
*
* @param string $connection
* @param int $level
* @return void
*/
public function rollback($connection, $level)
{
$this->transactions = $this->transactions->reject(function ($transaction) use ($connection, $level) {
return $transaction->connection == $connection &&
$transaction->level > $level;
})->values();
}
/**
* Commit the active database transaction.
*
* @param string $connection
* @return void
*/
public function commit($connection)
{
$this->transactions = $this->transactions->reject(function ($transaction) use ($connection) {
if ($transaction->connection == $connection) {
$transaction->executeCallbacks();
return true;
}
return false;
})->values();
}
文件位置:/laravel/framework/blob/8.x/src/Illuminate/Database/DatabaseTransactionRecord.php
<?php
namespace Illuminate\Database;
class DatabaseTransactionRecord
{
/**
* The name of the database connection.
*
* @var string
*/
public $connection;
/**
* The transaction level.
*
* @var int
*/
public $level;
/**
* The callbacks that should be executed after committing.
*
* @var array
*/
protected $callbacks = [];
/**
* Create a new database transaction record instance.
*
* @param string $connection
* @param int $level
* @return void
*/
public function __construct($connection, $level)
{
$this->connection = $connection;
$this->level = $level;
}
/**
* Register a callback to be executed after committing.
*
* @param callable $callback
* @return void
*/
public function addCallback($callback)
{
$this->callbacks[] = $callback;
}
/**
* Execute all of the callbacks.
*
* @return void
*/
public function executeCallbacks()
{
foreach ($this->callbacks as $callback) {
call_user_func($callback);
}
}
/**
* Get all of the callbacks.
*
* @return array
*/
public function getCallbacks()
{
return $this->callbacks;
}
}
msyql 本身不支持事务嵌套的,但是可以按照嵌套事务的思路变相实现事务多层嵌套。
开启事务时 先 mark 一个标志,每嵌套一次,就将该值加 1,但是开启事务这个操作只在 mark=1 时才真的去实现,其他只是累加。
而提交时,肯定是从最内层开始提交,每提交一次,mark 减去 1,直到 mark=1 时,才真的去实现提交。
回滚也是如此。
代码如下:
public function beginTransaction() {
++$this->transactions;
if ($this->transactions==1){
$this->pdo->beginTransaction();
}
}
public function rollBack() {
if ($this->transactions ==1) {
$this->transactions ==0;
$this->pdo->rollBack();
} else {
--$this->transactions;
}
}
public function commit() {
if ($this->transactions ==1){
$this->pdo->commit();
}
--$this->transactions;
}
在 MySQL 多次开启事务,出现了数据错乱问题,伪代码如下:
begin;
# 操作逻辑代码块1
begin;
# 操作逻辑代码块2
rollback;
执行完后出现了操作1
的数据真正写入,只有操作2
的数据回滚了。在第一个事务没有提交或回滚时,再开启第二个事务时,会自动提交第一个事务。
这明显不符合心理预期,而且也无法回滚一部分操作。那么问题来了,MySQL 支不支持事务嵌套呢?
这个问题很难准确回答支持还是不支持!
首先,调用多次begin
的写法,在 MySQL 里肯定是无法首先事务嵌套的。经过群内一位朋友的提醒,了解到 MySQL 中有一个叫 savepoint 和 rollback to 的语句。
示例代码:
DROP TABLE IF EXISTS `test`;
CREATE TABLE `test` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
begin;
insert into `test`(`name`) values(‘111‘);
SAVEPOINT p1;
insert into `test`(`name`) values(‘222‘);
ROLLBACK TO p1;
commit;
最终执行结果,test
表中只有 111 这个数据,实现了部分操作的回滚操作。同理也避免了多次开启事务,导致前一个事务被提交的问题。
可能savepoint
和rollback to
语句并不能称之为事务嵌套,也不能说 MySQL 是支持还是不支持事务嵌套。总之通过savepoint
和rollback to
,是可以用来达到一些事务嵌套特性的。
MySQL 嵌套事务、PHP+MySQL嵌套事务、ThinkPHP 嵌套事务、Laravel 嵌套事务
标签:synonym nod 通过 rollback 使用 any 回滚 @param 语句
原文地址:https://www.cnblogs.com/sochishun/p/14384577.html