对php异常调用SQL回滚


Calling SQL rollback on php exception

考虑以下场景:

  • 我使用mysqli扩展打开了一个到MySQL的新连接
  • 我开始交易
  • 我对MySQL数据库和一些php计算进行了一些查询
  • MySQL或php都可能引发一些异常
  • 我提交交易

(伪)代码:

$sql = new mysqli();
...
{ //some scope
    $sql->query("START TRANSACTION");
    ... 
    if (something)
        throw Exception("Something went wrong");
    ...
    $sql->query("COMMIT"); // or $sql->commit()
} //leave scope either normally or through exception
...
$sql->query( ... ); // other stuff not in transaction

如果以上所有内容都在某个大的try catch块中(或者甚至可能未捕获异常),则事务将保持未完成状态。如果我尝试在数据库上做更多的工作,那么这些操作将属于事务。

我的问题是:当我以异常的方式离开事务创建范围时,是否有一些可能的机制允许我自动发送$sql->rollback()


一些注意事项:

  • mysqli::autocommit与事务不同。这只是你打开或关闭自动提交的一个设置。相当于MySQL SET autocommit
  • mysqli::begin_transaction没有记录,我不知道它的确切行为是什么。例如,当mysqli对象死亡时,它会调用rollback

命令mysqli::begin_transaction$sql->query("START TRANSACTION");相同(面向对象的方式);

没有办法,在出现异常时自动回滚。

只有在一切都成功的情况下,你才能做出让步。如果没有,它将是一个"自动回滚"。但这样,如果你有不止一个承诺,你很快就会遇到麻烦。

所以你当前的代码已经准备好了。我会用完全OOP的方式:

$sql->begin_transaction();
try {
   $sql->query('DO SOMETHING');
   if(!true) {
       throw new 'Exception("Something went wrong");
   }
   $sql->commit();
}
catch ('Exception exception) {
   $sql->rollback();
}

您也可以编写自己的异常:

class SqlAutoRollbackException extends 'Exception {
    function __construct($msg, Sql $sql) {
        $sql->rollback();
        parent::__construct($msg);
    }
}

但你仍然需要抓住它。

您不能在异常时自动回滚,但只需一点代码就可以随心所欲。即使您已经在try-catch块中,也可以为您的事务部分嵌套它们,例如:

try {
    $sql->query("START TRANSACTION");
    ... 
    if (something)
        throw new PossiblyCustomException("Something went wrong");
    ...
    $sql->query("COMMIT"); 
} catch (Exception $e) { //catch all exceptions
    $sql->query("ROLLBACK");
    throw $e; //rethrow the very same exception object
}

即使使用最通用的catch Exception,异常的实际类型也是已知的。当您重新抛出时,PossiblyCustomException稍后仍然可以捕获它。因此,您已经拥有的所有处理都不受这个新的try-catch块的影响。

因此,如果出现问题,您需要提交事务或回滚事务,并且问题可能通过多种方式发生-通过mysql错误或php抛出异常。那么,为什么不在事务开始时设置一个标志,在事务准备提交时设置该标志,并在进程完成时检查标志的状态,看看是否需要回滚呢?

如果你害怕全局范围的变量,你可以使用一个过于复杂的Singleton类或某种静态变量,但这里有一个基本想法:

function main() {
  global $commit_flag = false;
  start_transaction();
  doEverything();
  if ($commit_flag) {
    commit_transaction();
  } else {
    rollback_transaction();
  }
}
function doEverything() {
  global $commit_flag;
  try {
    /* do some stuff that may cause errors */
    $commit_flag = true;
  } catch { ... }
}

您可以使用DECLARE处理mysql异常。。。HANDLER语法,但我觉得通过PHP处理它们不合适!

BEGIN
        DECLARE EXIT HANDLER FOR SQLEXCEPTION  ROLLBACK;
        START TRANSACTION;
          INSERT INTO Users ( user_id ) VALUES('1');
          INSERT INTO Users ( user_id ) VALUES('2');
        COMMIT;
END;

更多信息可在此处找到:http://dev.mysql.com/doc/refman/5.5/en/declare-handler.html