【问题标题】:MySQLi Transaction Spaghetti HellMySQLi 事务意大利面地狱
【发布时间】:2013-06-24 11:07:06
【问题描述】:

我有一堂课,里面有一百多个函数/方法。其中许多需要在事务中运行 MySQLi 查询。所以,在一个简单的形式中,大部分函数看起来像这样:

class MyClass {
    private $connection = ...; // mysqli connection
    public function a () {
        $this->connection->autocommit(FALSE);
        // do something and run multiple queries
        $this->connection->commit();
    }
    public function b () {
        $this->connection->autocommit(FALSE);
        // do something and run multiple queries
        $this->connection->commit();
    }
}
// to run the code:
$myclass = new MyClass();
$myclass->a(); // all queries are run inside a transaction
$myclass->b(); // all queries are run inside a transaction

这个概念很简单:每次我运行一个公共函数/方法时,它都会启动一个事务并在结束函数/方法之前提交。 现在,我的问题是......如果我想在另一个方法中运行一个方法,同时保持两个方法都是公开的并且可以被类外的调用者调用怎么办?例如:

class MyClass {
    private $connection = ...; // mysqli connection
    public function a () {
        $this->connection->autocommit(FALSE);
        // do something and run multiple queries
        $this->connection->commit();
    }
    public function b () {
        $this->connection->autocommit(FALSE);
        // do something and run multiple queries
        $this->a(); // running function a inside function b here
        $this->connection->commit();
    }
}
// to run the code:
$myclass = new MyClass();
$myclass->a(); // all queries are run inside a transaction
$myclass->b(); // transaction is not working as I initially wished. This function consists of more than one commit, hence, not everything is run inside one transaction

使用额外的变量进行手动 if/else 检查,我想我可以通过两个函数解决上述问题。但是,我的问题是我有多个(可能将近一百个)功能/方法与上述类似。我不认为我可以使用一些 if/else 来处理如此复杂的事务意大利面地狱问题。

我该如何解决这个问题?

PS:

以上只是我的课程的简化。为了让事情变得更复杂,在我的课堂上我不只是使用:

function func() {
    $this->connection->autocommit(FALSE);
    // do something
    $this->connection->commit();
}

相反,我做了这样的事情:

function func() {
    if ($result = $this->connection->query('SELECT @@autocommit')) {
        $row = mysqli_fetch_row($result);
        $originalAutoCommitValue = $row[0];
        mysqli_free_result($result);
    }
    if (!$originalAutoCommitValue) {
        $this->connection->autocommit(FALSE);
    }
    // do something...
    $this->connection->commit();
    $this->connection->autocommit($originalAutoCommitValue);
}

这将允许我的类中的函数/方法启动事务、提交事务并将自动提交状态返回到之前。这确实很笨拙,但我希望我班的用户可以选择是否在课外使用事务/提交。我希望这种方法在使用我的任何类的方法之前不会影响它们原来的自动提交状态。

更新

我在类中添加了以下属性和两个函数:

private $connectionTransactionStack = 0;
private $connectionOriginalAutoCommitState = NULL;

private function startTransaction() {
    if (is_null($this->connectionOriginalAutoCommitState)) {
        if ($result = $this->connection->query('SELECT @@autocommit')) {
            $row = mysqli_fetch_row($result);
            $this->connectionOriginalAutoCommitState = $row[0];
            mysqli_free_result($result);
        }
    }
    if ($this->connectionOriginalAutoCommitState === 1) {
        $this->connection->autocommit(FALSE);
    }
    $this->connectionTransactionStack++;
}
private function commitTransaction() {
    $this->connectionTransactionStack--;
    if ($this->connectionTransactionStack < 1) {
        $this->connectionTransactionStack = 0;
        if ($this->connectionOriginalAutoCommitState === 1) {
            $this->connection->commit();
            $this->connection->autocommit($this->connectionOriginalAutoCommitState);
        }
    }
}

所以,现在当我想运行原始函数时,我可以这样做:

class MyClass {

    public function a () {
        $this->startTransaction();
        // do something and run multiple queries
        $this->commitTransaction();
    }
    public function b () {
        $this->startTransaction();
        // do something and run multiple queries
        $this->a(); // running function a inside function b here
        $this->commitTransaction();
    }
}

如果我在任何方法中启动任何事务,只要我提交所有事务,非最终的 $this->commitTransaction() 将不会运行真正的 mysqli->commit(),但最终的 $this->commitTransaction( ) 将在必要时运行 mysqli->commit (取决于在我的类中第一次运行任何方法之前的初始状态)。所以如果我想跑:

$this->a();

$this->b();

,这根本不重要,因为有一个堆栈可以计算我将启动的事务。然而,实际上,只有一个事务要启动和提交。

这可以解决我上面的问题吗?

【问题讨论】:

  • 也许您可以为您所拥有的功能展示使用 3-4 个 REAL 代码,我们也许能够找出一个下降来实现您想要的。
  • 我不介意在这里展示真实的代码,但我想说这样做没有什么意义,因为在现实世界的项目中,上述要求对于 PHP-MySQL 交互来说是很常见的,特别是当需要交易来实现某事时。例如,假设我有一系列 SELECT 查询,每个查询可能依赖于前一个查询的结果。另一个例子是运行一个 SELECT 查询,然后执行一个 UPDATE,这取决于前一个 SELECT 查询的结果。
  • 你是否总是对 startTransaction() 和 commitTransaction() 做同样的事情,但不明确地调用了哪个函数?

标签: php transactions mysqli commit autocommit


【解决方案1】:

一种选择可能是删除

$this->connection->autocommit(FALSE);
$this->connection->commit();

在每个相关的函数中 (a,b) 然后将它们包装在您的函数调用周围。

class MyClass {
    private $connection = ...; // mysqli connection
    public function a () {
        // do something and run multiple queries
    }
    public function b () {
        // do something and run multiple queries
        $this->a(); // running function a inside function b here
    }
}
// to run the code:
$myclass = new MyClass();

$myclass->connection->autocommit(FALSE);
$myclass->a(); // all queries are run inside a transaction
$myclass->b(); 
$this->connection->commit();

更新:

或者更好,使用包装函数来调用类中的函数:

$myclass->doTransaction('a'); 
$myclass->doTransaction('b'); 

function doTransaction($functionCall) {
    //Do some sanitizing of the functionCall-variable
    $this->connection->autocommit(FALSE);
    call_use_func($functionCall); 
    $this->connection->commit();
}

【讨论】:

  • 嗯...这在逻辑上可能是正确的,但不是我想要的。你看,我在函数中做了所有那些提交主要是因为我希望类的用户使用函数而不用担心事务......但是当然,如​​果他们愿意,他们可以选择处理事务,因为显示在我的问题的“PS”部分。
  • @user2526586 - 好的,但是你可以做一个包装函数来调用你想要的实际函数。等一下,我应该更新答案。
  • 你的意思是调用:$myclass->doTransaction('a', $param1, $param2, $param3, $param4...);在 doTransaction 内部: call_user_func($functionCall, $functionParam1, $functionParam2, $functionParam3, $functionParam4...);如果是这样的话……嗯……嗯,我想这可行,只是它有点凌乱,对班级的其他用户来说不是很友好……
  • @user2526586 - 开发人员必须完成这项工作,因为只有他才能知道单个事务中的内容。您的函数类似于分组自动提交,并且总是会中断更高级别事务的使用。
  • @user2526586 - 是的,我的意思是(我没有包含任何 $params,因为函数 a 和 b 中没有任何参数)。它可能很乱,但我真的不是一个向导 :-) 如果你想正确地完成它,那么你将不得不完全按照你的规范重写代码。从庞大的丑陋代码库中获取优质代码没有捷径可走。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-06
  • 1970-01-01
相关资源
最近更新 更多