我正在寻找一种使用PHP在MySQL中解决“嵌套”事务的方法,正如你所知,在MySQL文档中写道,不可能在事务内再开启一个事务 (Mysql transactions within transactions)。我尝试使用http://php.net/manual/en/pdo.begintransaction.php中建议的Database类,但不幸的是,这对我来说是错误的,因为它的计数器范围是对象级别而不是类级别。为了解决这个问题,我创建了这个类(TransactionController),它的计数器(命名为$nest)是静态的,并且它带来了必要的类级别,以使事务“线性化”(所谓的“线性化”是指:看起来是嵌套的,但如果你仔细看它并没有嵌套),那么事务将正常工作,你认为呢?(请参考结尾的示例,CarOwner)
class TransactionController extends \\PDO {
public static $warn_rollback_was_thrown = false;
public static $transaction_rollbacked = false;
public function __construct()
{
parent :: __construct( ... connection info ... );
}
public static $nest = 0;
public function reset()
{
TransactionController :: $transaction_rollbacked = false;
TransactionController :: $warn_rollback_was_thrown = false;
TransactionController :: $nest = 0;
}
function beginTransaction()
{
$result = null;
if (TransactionController :: $nest == 0) {
$this->reset();
$result = parent :: beginTransaction();
}
TransactionController :: $nest++;
return $result;
}
public function commit()
{
$result = null;
if (TransactionController :: $nest == 0 &&
!TransactionController :: $transaction_rollbacked &&
!TransactionController :: $warn_rollback_was_thrown) {
$result = parent :: commit();
}
TransactionController :: $nest--;
return $result;
}
public function rollback()
{
$result = null;
if (TransactionController :: $nest >= 0) {
if (TransactionController :: $nest == 0) {
$result = parent :: rollback();
TransactionController :: $transaction_rollbacked = true;
}
else {
TransactionController :: $warn_rollback_was_thrown = true;
}
}
TransactionController :: $nest--;
return $result;
}
public function transactionFailed()
{
return TransactionController :: $warn_rollback_was_thrown === true;
}
// to force rollback you can only do it from $nest = 0
public function forceRollback()
{
if (TransactionController :: $nest === 0) {
throw new \PDOException();
}
}
}
class CarData extends TransactionController {
public function insertCar()
{
try {
$this->beginTransaction();
... (operations) ...
$this->commit();
}
catch (\PDOException $e) {
$this->rollback();
}
}
}
class PersonData extends TransactionController {
public function insertPerson( $person=null )
{
try {
$this->beginTransaction();
... (operations) ...
$this->commit();
}
catch (\PDOException $e) {
$this->rollback();
}
}
}
class CarOwnerData extends TransactionController {
public function createOwner()
{
try {
$this->beginTransaction();
$car = new CarData();
$car->insertCar();
$person = new PersonData();
$person->insertPerson();
... (operations) ...
$this->commit();
}
catch (\PDOException $e) {
$this->rollback();
}
}
}
$sellCar = new CarOwnerData();
$sellCar->createOwner();
更新1: 在TransactionController中添加了静态属性$warn_rollback_was_thrown
,以便在执行某些时刻事务失败但未回滚时发出警告。
更新2: 当事务在某个时刻失败时,您可以让代码继续运行到结束,也可以使用forceRollback()
进行彻底停止。例如,可以参考以下代码:
<?php // inside the class PersonData
public function insertMultiplePersons( $arrayPersons )
{
try {
$this->beginTransaction();
if (is_array( $arrayPersons )) {
foreach ($arrayPersons as $k => $person) {
$this->insertPerson( $person );
if ($this->transactionFailed()) {
$this->forceRollback();
}
}
}
$this->commit();
}
catch (\PDOException $e) {
$this->rollback();
}
} ?>
$nest
之外:嵌套事务应该支持以下操作:start trans 1, 做一些事情, start trans 2, rollback trans 2, 可选地重试 trans 2 或者做其他事情, commit trans 1
,你的代码不能支持这样的操作(因为mysql不支持)。只有当内部回滚发生时,你才需要跳转到 trans 1 的异常处理部分(你可以通过重新引发异常来实现),否则在$person->insertPerson();
失败后,代码... (operations) ...
将会在没有任何事务的情况下执行。 - Solarflare$this->beginTransaction();
应该改成$parent->beginTransaction();
(否则它永远不会调用 pdo,而是进入一个循环)。在rollback
中的TransactionController :: $nest--;
必须在测试== 0
之前(否则它永远不会回滚),commit
同理。并且forceRollback
应该设置一个变量,在rollback
中检查并重新抛出... - Solarflare$nest > 0
(否则它只会强制向下一层,除非你想要那样),则可以再次使用。在某处您可能想要添加一个$next < 0
的检查。在您的第一个启动之前简单地忘记了commit
将不会引发错误,但是在代码的其余部分中行为不正确(提交级别1)。在rollback
中您可能仍然需要一个reset
(并且出于安全起见,在commit
中也需要),否则您无法在级别0上开始新的事务-至少如果您希望它能够这样做的话。并且由于重新抛出异常不是默认行为,因此您需要在commit
中进行检查,如果... - Solarflare