PDO теряет транзакцию до фиксации / отката

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

Мне кажется, что причина этого в том, что я создаю экземпляр класса 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-> TransactionsStarted () несколько раз. Это попытка увидеть, когда я потеряю транзакцию. Я ожидаю увидеть мое сообщение, за которым следует 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

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

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

РЕДАКТИРОВАТЬ
Поэтому я сделал несколько важных изменений в коде и смог более точно определить, когда я теряю активную транзакцию.

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

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-> update это то, что мы отлаживаем здесь, это метод update

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

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

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

РЕДАКТИРОВАТЬ добавление функции 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;
}
}

РЕДАКТИРОВАТЬ добавление метода создания

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

Решение

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

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

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

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

0

Другие решения

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

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

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

Смотрите также мой ответ на Как обнаружить, что транзакция уже началась?


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

По вопросам рекламы [email protected]