考虑以下场景:
- 我使用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
与事务不同。这只是你打开或关闭自动提交的一个设置。相当于MySQLSET 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