Нужна помощь с вложенными атомарными операциями, включающими транзакции PDO - PullRequest
5 голосов
/ 28 апреля 2010

У меня есть два отдельных модуля, которые можно использовать независимо, но модуль 2 зависит от модуля 1.

Модуль 2 имеет операцию, которая должна быть атомарной, и он вызывает операцию в модуле 1, которая также должна быть атомарной.

Предполагая, что я установил 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;
        }
    }
}

Я не уверен, что лучший способ это сделать - вложенная операция обязательно будет вызвана независимо, и абсолютно должна быть атомарной, если вызывается сама по себе. Нежелательно возлагать ответственность за управление транзакцией и сохранение атомарности на пользователя класса, поскольку я уверен, что пользователи класса никогда не будут применять его.

Ответы [ 2 ]

4 голосов
/ 28 апреля 2010

Вам нужно создать свой собственный класс, который расширяет 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 голос
/ 21 февраля 2012

Решение Арха, хотя и близко, является неверным , потому что 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 (), хотя остерегайтесь того, что неявные коммиты (CREATE TABLE и т. Д.) В MySQL могут испортить надежность этого.

Это сообщение в блоге за 2008 год на самом деле имеет реализацию того, что я говорю.

Этот код будет пытаться использовать код SAVEPOINT только в том случае, если вы используете драйвер базы данных, который его поддерживает

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...