需要有关涉及PDO事务的嵌套原子操作的帮助

时间:2010-04-28 12:45:44

标签: php pdo

我有两个可以独立使用的不同模块,但Module2依赖于Module1。

Module2有一个需要是原子的操作,它调用Module1中也需要是原子的操作。

假设我已将PDO :: ATTR_ERRMODE设置为PDO:ERRMODE_EXCEPTION,以下严格通用化和剪切代码会产生以下结果: PHP致命错误:未捕获异常'PDOException',消息'已存在活动事务'

模块1:

<?php
class Module1
{
    ...
    public function atomicOperation($stuff)
    {
        $this->pdo->beginTransaction();
        try {
            $stmt = $this->pdo->prepare(...);
            ...
            $this->pdo->commit();
        }
        catch (Exception $ex) {
            $this->pdo->rollBack();
            throw $ex;
        }
    }
}

单词数:

<?php
class Module2
{
    public $module1;
    ...
    public function atomicOperation($stuff)
    {
        $this->pdo->beginTransaction();
        try {
            $stmt = $this->pdo->prepare(...);
            ...
            $this->module1->atomicOperation($stuff);
            ...
            $this->pdo->commit();
        }
        catch (Exception $ex) {
            $this->pdo->rollBack();
            throw $ex;
        }
    }
}

我不确定最好的方法 - 嵌套操作肯定会被独立调用,绝对必须在自己调用时才是原子的。将责任放在类'用户上以管理事务并保持原子性是不可取的,因为我确信该类的用户永远不会强制执行它。

2 个答案:

答案 0 :(得分:4)

您需要创建自己的类,扩展PDO并管理事务。 类似的东西:

<?php
class Db extends PDO{
  private $_inTrans = false;

  public function beginTransaction(){
    if(!$this->_inTrans){
      $this->_inTrans = parent::beginTransaction();
    }
    return $this->_inTrans;
  }

  public function commit(){
    if($this->_inTrans){
      $this->_inTrans = false;
      return parent::commit();
    }
    return true;
  }

  public function rollBack(){
    if($this->_inTrans){
      $this->_inTrans = false;
      return parent::rollBack();
    }
    return true;
  }

  public function transactionStarted(){
    return $this->_inTrans;
  }

}

如果在那里启动某些事务,您仍需要检查所有传递的查询。

第1单元:

<?php
class Module1
{
    ...
    public function atomicOperation($stuff)
    {
        $transactionAlreadyStarted = $this->pdo->transactionStarted();
        if(!$transactionAlreadyStarted){
            $this->pdo->beginTransaction();
        }
        try {
            $stmt = $this->pdo->prepare(...);
            ...

            if(!$transactionAlreadyStarted && $this->pdo->transactionStarted()){
                $this->pdo->commit();
            }
        }
        catch (Exception $ex) {
            if($this->pdo->transactionStarted()){
                $this->pdo->rollBack();
            }
            throw $ex;
        }
    }
}

第2单元:

<?php
class Module2
{
    public $module1;
    ...
    public function atomicOperation($stuff)
    {
        $transactionAlreadyStarted = $this->pdo->transactionStarted();
        if(!$transactionAlreadyStarted){
            $this->pdo->beginTransaction();
        }
        try {
            $stmt = $this->pdo->prepare(...);
            ...
            $this->module1->atomicOperation($stuff);
            ...
            if(!$transactionAlreadyStarted && $this->pdo->transactionStarted()){
                $this->pdo->commit();
            }
        }
        catch (Exception $ex) {
            if($this->pdo->transactionStarted()){
                $this->pdo->rollBack();
            }
            throw $ex;
        }
    }
}

答案 1 :(得分:1)

Arkh的解决方案尽管很接近,但错误因为commit()和rollback()基本上是撒谎。当没有真正发生任何事情时,调用rollback()或commit()可能会返回true。

相反,您应该使用SAVEPOINTs

  

在PostgreSQL,Oracle,Microsoft SQL Server,MySQL,DB2,SQLite(自3.6.8),Firebird和Informix(自版本11.50xC3)以来的数据库系统中,某些形式或其他形式支持保存点。保存点也在SQL标准中定义。

在自定义数据库类中,您覆盖commit,rollback和beginTransaction()并在适当的地方使用SAVEPOINT。您也可以尝试实现inTransaction(),但要注意MySQL中的隐式提交(CREATE TABLE等)会破坏它的可靠性。

blog post from 2008实际上有我所说的实现。

  

如果您使用支持它的数据库驱动程序,此代码将仅尝试使用SAVEPOINT代码