2014-09-23 3 views
0

Я пытаюсь использовать транзакции с mysql с помощью PDO для выполнения работы. Проблема, с которой я столкнулась, заключается в том, что транзакция ломается, прежде чем я доберусь до фиксации. Я знаю это, потому что я эхо-функция inTransaction() в соединении.PDO, потерявший транзакцию перед фиксацией/откатом

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

Инстанцирование мой класс

$pdo = new PdoDatabase; 
$pdo->beginTransaction(); 
echo "first ".$pdo->transactionStarted()."<br />"; 

PdoDatabase класс

public function __construct(){ 
    $dsn = 'mysql:host='.DB_HOST.';dbname='.DB_NAME; 
    $options = array(PDO::ATTR_PERSISTENT => TRUE, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION); 
    try{ 
     $this->_connection = new PDO($dsn, DB_USER, BD_PASS, $options); 
    } catch (PDOException $e){ 
     $this->_error = $e->getMessage(); 
    } 
} 

public function query($q){ 
    if($this->_error != ''){ 
     echo $this->_error; 
    } else { 
     $this->_stmt = $this->_connection->prepare($q); 
    } 
} 

public function bind($param, $value, $type = null){ 
    //echo "<br>".$value."<br>"; 
    if (is_null($type)) { 
     switch (true) { 
     case is_int($value): 
      $type = PDO::PARAM_INT; 
      break; 
     case is_bool($value): 
      $type = PDO::PARAM_BOOL; 
      break; 
     case is_null($value): 
      $type = PDO::PARAM_NULL; 
      break; 
     default: 
      $type = PDO::PARAM_STR; 
     } 
    } 
    $this->_stmt->bindValue($param, $value, $type); 
} 

public function execute($class = null){ 
    $object_array = array(); 

    if($class !== null){ 
     if($this->_stmt->execute()){ 
      $this->_stmt->setFetchMode(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, $class, null); 
      while($row = $this->returnRow()){ 
       $object_array[] = $class::instantiate($row); 
      } 
     } 
     return $object_array; 
    } else { 

     return $this->_stmt->execute(); 
    } 
} 

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

Как я использую его (часть)

if(isset($_POST['id']) && $_POST['id'] != ''){ 
       echo "id exists ".$pdo->transactionStarted()."<br />"; 
       $bidder->getBidderById($_POST['id']); 
       echo "step 1 ".$pdo->transactionStarted()."<br />"; 
       $bidder = $bidder->getList(0); 
       echo "step 2 ".$pdo->transactionStarted()."<br />"; 
       $old_bidder = clone $bidder; 
       echo "step 3 ".$pdo->transactionStarted()."<br />"; 

       $bidder_phones->getPhones($_POST['id']); 
       echo "step 4 ".$pdo->transactionStarted()."<br />"; 
       $bidder_phones = $bidder_phones->getList(); 
       echo "step 5 ".$pdo->transactionStarted()."<br />"; 
       if($_POST['phone'] == ''){ 
        // check to see if there are any phone numbers in the database already and delete if there is 
        foreach($bidder_phones as $bp){ 
         $q = "delete from bidder_phones where id = :id"; 
         $pdo->query($q); 
         $pdo->bind(":id", $bp->getId()); 
         $pdo->execute(); 
         //$bp->remove(); 
        } 
       } else { 
        echo "phone to check ".$pdo->transactionStarted()."<br />"; 
        $old_phone_numbers = array(); 
        $new_phone_numbers = explode(',', $_POST['phone']); 
        foreach($bidder_phones as $bp){ 
         // remove any unused phone numbers 
         if(!in_array($bp, $new_phone_numbers)){ 
          $q = "delete from bidder_phones where id = :id"; 
          $pdo->query($q); 
          $pdo->bind(":id", $bp->getId()); 
          $pdo->execute(); 
          //$bp->remove(); 
         } 
         // push to an array to test the new numbers 
         array_push($old_phone_numbers, $bp->getPhone()); 
        } 
        foreach($new_phone_numbers as $phone){ 
         // adding new phone numbers 
         if(!in_array($phone, $old_phone_numbers)){ 
          $new_phone = new BidderPhone; 
          $new_phone->setPhone($phone); 
          $new_phone->setBidderId($_POST['id']); 
          $pdo->save('BidderPhones', $new_phone); 
          //$new_phone->save(); 
         } 
        } 
       } 

Как вы можете видеть, я эхо $ pdo- > transactionStarted() несколько раз. Это попытка увидеть, когда я потеряю транзакцию. То, что я ожидаю увидеть, - это мое сообщение, за которым следует 1, чтобы показать, что транзакция по-прежнему активна. Это то, что я получаю:

first 1 
id exists 1 
step 1 
step 2 
step 3 
step 4 
step 5 
phone to check 
in save 
before create 

Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[42S02]: Base table or view not found: 1146 Table 'clickbid.bidder_phoneses' doesn't exist' in /var/www/classes/PdoDatabase.php:59 Stack trace: #0 /var/www/classes/PdoDatabase.php(59): PDOStatement->execute() #1 /var/www/classes/PdoDatabase.php(158): PdoDatabase->execute() #2 /var/www/classes/PdoDatabase.php(187): PdoDatabase->getFields('BidderPhones') #3 /var/www/classes/PdoDatabase.php(176): PdoDatabase->create('BidderPhones', Object(BidderPhone), false) #4 /var/www/admin/data/post_data.php(284): PdoDatabase->save('BidderPhones', Object(BidderPhone)) #5 {main} thrown in /var/www/classes/PdoDatabase.php on line 59 

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

Заранее благодарим за любую помощь.

EDIT Итак, я внесла некоторые существенные изменения в код и смог более точно определить, когда теряю активную транзакцию.

Мой метод сохранения выглядит следующим образом

public function save($table, $object, $old_object = null){ 
    echo "in save ".$this->transactionStarted()."<br />"; 
    if($object->_id != ''){ 
     echo "before update ".$this->transactionStarted()."<br />"; 
     //return $this->update($table, $object, $old_object, $transaction); 
     if($this->update($table, $object, $old_object)){ 
      echo "update true ".$this->transactionStarted()."<br />"; 
      return true; 
     } else { 
      echo "update false ".$this->transactionStarted()."<br />"; 
      return false; 
     } 
    } else { 
     echo "before create ".$this->transactionStarted()."<br />"; 
     //return $this->create($table, $object, $transaction); 
     if($this->create($table, $object)){ 
      echo "create true ".$this->transactionStarted()."<br />"; 
      return true; 
     } else { 
      echo "create false ".$this->transactionStarted()."<br />"; 
      return false; 
     } 
    } 
} 

В данном конкретном случае есть _id идет от предмета $ this-> Обновление, что мы здесь отладки является метод обновления

private function update($table, $object, $old){ 
    echo "in update ".$this->transactionStarted()."<br />"; 
    $audit = new PdoDatabase; 
    echo "after new ".$this->transactionStarted()."<br />"; 
    $aq = "insert into audit_trails 
      (table_name, table_id, user_id, field_name, original_value, new_value) values 
      (:table_name, :table_id, :user_id, :field_name, :original_value, :new_value)"; 
    echo "before query ".$this->transactionStarted()."<br />"; 
    $audit->query($aq); 
    echo "after query ".$this->transactionStarted()."<br />"; 
    //$update = new PdoDatabase;  

    $binding = array(); 
    echo "before field_names ".$this->transactionStarted()."<br />"; 
    $field_names = self::getFields($table); 
    echo "after field_names ".$this->transactionStarted()."<br />"; 

    $uc = new UserConfig; 
    $uc->getConfig(); 
    $user = $uc->getList(0); 
    echo "before foreach ".$this->transactionStarted()."<br />"; 
    foreach($field_names as $field){ 
     $thisField = "_".$field['Field']."<br>"; 
     $getField = 'get'.self::to_camel_case($field['Field']); 
     $method = $getField; 
     $class = self::to_camel_case($table); 
     $field_list = ''; 

     if($field['Field'] == 'id'){ 
      $where = 'where id = :id'; 
      $binding[':id'] = ($object->$getField()); 
     } else { 

      if(method_exists($class, $method)){ 
       if($object->$getField() != $old->$getField()){ 
        $field_list .= $field['Field']."= :".$field['Field'].", "; 
        $binding[':'.$field['Field']] = $object->$getField(); 

        $audit->bind(':table_name', $table); 
        $audit->bind(':table_id', $object->getId()); 
        $audit->bind(':user_id', $user->getUserId()); 
        $audit->bind(':field_name', $thisField); 
        $audit->bind(':original_value', $object->$getField()); 
        $audit->bind(':new_value', $old->$getField()); 

        echo "before audit execute ".$this->transactionStarted()."<br />"; 
        $audit->execute(); 
        echo "after audit execute ".$this->transactionStarted()."<br />"; 

       } 
      } 
     } 
    } 
    echo "before binding ".$this->transactionStarted()."<br />"; 
    if(count($binding) > 1){ 
     $q = "update ".self::singularToPlural($table)." set "; 
     foreach($binding as $key => $value){ 
      if($key != ':id'){ 
       $q .= str_replace(':', '', $key)." = ".$key.", "; 
      } 
     } 
     $q = rtrim($q, ", "); 
     $q .= ' '.$where; 

     //$update->query($q); 
     echo "before this query ".$this->transactionStarted()."<br />"; 
     $this->query($q); 
     echo "after this query ".$this->transactionStarted()."<br />"; 
     /*if($transaction && !$this->_stmt->inTransaction()){ 
      $this->_stmt->beginTransaction(); 
     }*/ 

     foreach($binding as $key => $value){ 
      //$update->bind($key, $value); 
      $this->bind($key, $value); 
     } 
     //$update->bind($id); 
     //return $update->execute(); 
     echo "before this execute ".$this->transactionStarted()."<br />"; 
     $stupid = $this->execute(); 
     echo "after this execute ".$this->transactionStarted()."<br />"; 
     return $stupid; 
    } else { 
     echo "before return true ".$this->transactionStarted()."<br />"; 
     return true; 
    } 

} 

и выход

first 1 
in save 1 
before update 1 
in update 1 
after new 1 
before query 1 
after query 1 
before field_names 1 
before execute 1 
after execute 1 
after field_names 1 
before foreach 1 
before audit execute 1 
before execute 1 
after execute 1 
after audit execute 1 
before binding 1 
before this query 1 
after this query 1 
before this execute 1 
before execute 1 
after execute 1 
after this execute 1 
update true 
the save 1 
second 
after save 
before commit 

Fatal error: Uncaught exception 'PDOException' with message 'There is no active transaction' in /var/www/classes/PdoDatabase.php:86 Stack trace: #0 /var/www/classes/PdoDatabase.php(86): PDO->commit() #1 /var/www/admin/data/post_data.php(403): PdoDatabase->commit() #2 {main} thrown in /var/www/classes/PdoDatabase.php on line 86 

Из этого я могу видеть, что активная транзакция теряется, когда мы возвращаемся из обновления для сохранения методов, я просто не знаю, почему это так.

Еще раз спасибо.

EDIT добавив функцию getBidderById

public function getBidderById($bidder_id){ 
    $pdo = new PdoDatabase; 

    $q = "select * 
      from bidders Bidder 
      where Bidder.id = :bidder_id 
      limit 1"; 

    $pdo->query($q); 
    $pdo->bind(":bidder_id", $bidder_id); 
    $this->_bidders = $pdo->execute('Bidder'); 

    if(!empty($this->_bidders)){ 
     return true; 
    } else { 
     return false; 
    } 
} 

EDIT добавив метод создания

private function create($table, $object){ 
    //$insert = new PdoDatabase; 

    $field_names = self::getFields($table); 

    foreach($field_names as $field){ 
     $getField = 'get'.self::to_camel_case($field['Field']); 
     $method = $getField; 
     $class = self::to_camel_case($table); 

     if(method_exists($class, $method)){ 
      if($field['Field'] != 'id'){ 
       $fields = $field['Field'].", "; 
       $binding_fields = ":".$field['Field'].", "; 
       $binding[':'.$field['Field']] = $object->$getField(); 
      } 
     } 
    } 
    $fields = rtrim($fields, ", "); 
    $binding_fields = rtrim($binding_fields, ", "); 

    $iq = "insert into ".self::singularToPlural($table)." 
      (".$fields.") values 
      (".$binding_fields.")"; 
    $this->query($iq); 
    /*if($transaction && !$this->_stmt->inTransaction()){ 
     $this->_stmt->beginTransaction(); 
    }*/ 

    foreach($binding as $key => $value){ 
     $this->bind($key, $value); 
    } 

    if($this->execute()){ 
     $object->setId($this->getConnection()->lastInsertId()); 
     return true; 
    } else { 
     return false; 
    } 

} 
+0

Что происходит в $ bid- Gervs

+0

$ bid-> getBidderById ($ _ POST ['id']) запрашивает базу данных для этого участника торгов, он заполняет экземпляр участника, который будет использоваться позже, чтобы обновить участника торгов, если что-либо отличается, следовательно, $ old_bidder = quone $ bid. Оба они передаются методу сохранения и сравниваются друг с другом, чтобы определить, что необходимо обновить. –

+0

Нет заявлений DDL ?, можете ли вы показать функцию? – Gervs

ответ

0

HMz, я заметил, что вы инстанцировании PdoDatabase в функции и использовать $ this-> transactionStarted ().

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

if($this->create($table, $object)){ 
     echo "create true ".$this->transactionStarted()."<br />"; 
     return true; 
    } 

Если использовать DDL заявление (CREATE, ALTER, DROP, TRUNCATE и т.д.) в этой функции коммит будет также применяться

+0

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

+0

@ Jamie Просмотреть мое редактирование – Gervs

+0

create не используется, это просто мое имя метода. единственное, что сделано в этом методе, - это вставка, я добавлю это к моему сообщению выше в секунду. –

0

Некоторые заявления в MySQL вызвать неявную фиксацию. Любая транзакция, которую вы, возможно, выполняете, совершенна. Например, все заявления DDL делают это. См. https://dev.mysql.com/doc/refman/5.6/en/implicit-commit.html для получения дополнительной информации.

Удивительно сложно узнать, есть ли у вас активная транзакция в сеансе. Между операторами, которые вызывают неявное коммитирование, и возможностями, которые может быть запросом может быть «COMMIT» (не проходя через какую-либо функцию commit(), а затем вместо функции query()), клиент не может точно знать, есть ли у них все еще сделка будет.

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

Смотрите также мой ответ на How do detect that transaction has already been started?


Re комментарий:

Для MySQL, PDO::inTransaction() является не надежным, потому что драйвер PDO MySQL не реализует метод mysql_handle_in_transaction(). Нет поддержки в MySQL C API для опроса состояния транзакции текущего сеанса.

Поэтому класс PDO пытается сделать его лучшее предположение, используя внутреннюю переменную, которая устанавливается в 1, если вы звоните $dbh->beginTransaction(), и установить на 0, если вы звоните $dbh->commit() или $dbh->rollback().

Но если оператор DDL вызывает неявное коммитирование, драйвер не имеет уведомления о завершении транзакции. Таким образом, внутренняя переменная в PDO может перестать синхронизироваться с реальностью. Это также может случиться, если вы вызываете $dbh->query('COMMIT'), минуя функции PDO для управления транзакциями.

Другие драйверы PDO, например драйвер PostgreSQL, реализуют средства для получения текущего состояния транзакции.


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

Я не могу сказать, что происходит в вашем случае. У вас все еще есть несколько строк вашего отладочного вывода, для которого вы не поделились кодом.

+0

так вы говорите, что метод inTransaciton() для соединения ненадежный? Поскольку я использую его и, похоже, работает со мной, он возвращает 1 (или true), если я совершаю транзакцию. Который из всех моих примеров выше показывает, что это происходит. Вот почему я чувствую, что транзакция ломается после того, как я выполняю оператор запроса и возвращаюсь к вызывающему, что не имеет для меня смысла, потому что ничего не происходит, за исключением того, что возвращается true (при условии, что выполнение выполнено успешно), и транзакция по-прежнему активна только до возвращения –

Смежные вопросы