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

问题描述 投票:5回答:2

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

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

假设我已将PDO :: ATTR_ERRMODE设置为PDO:ERRMODE_EXCEPTION,则以下经过大量泛化和删节的代码将产生此结果:PHP致命错误:消息为“已存在活动事务”的未捕获异常'PDOException'

Module1:

<?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;
        }
    }
}

Module2:

<?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;
        }
    }
}

我不确定执行此操作的最佳方法-嵌套操作一定会被独立调用,并且单独调用时绝对必须是原子的。不希望让类的用户来管理事务并保留原子性,因为我敢肯定类的用户将永远不会执行它。

php pdo
2个回答
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;
        }
    }
}

2
投票

Arkh的解决方案,尽管正确,但是不可靠的,因为commit()rollback()基本上是lying”。当什么都没发生时,调用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代码。>

© www.soinside.com 2019 - 2024. All rights reserved.