Я просмотрел ресурсы для rollBack()
, commit()
и различные транзакции, но я не могу найти, можно ли вызвать rollBack()
после того, как commit()
уже был вызван.
Ситуация такова:
У меня есть две разные базы данных: $dbu = new PDO(..db1..)
и $dbb = new PDO(..db2..)
В обеих базах данных есть таблицы, которые обновляются в рамках одной функции. Операция all or none — либо все таблицы успешно обновлены, либо ни одна.
Используя две отдельные транзакции, если транзакция для $dbu
успешно завершена, но транзакция для $dbb
завершается неудачно, я должен отменить то, что было сделано в первой транзакции:
Блок кода 1
$dbu->beginTransaction();
try{
$stmt = $dbu->prepare(...);
$stmt->execute();
// stuff
$dbu->commit();
}catch(Exception $e){
// do stuff
$dbu->rollBack();
exit();
}
$dbb->beginTransaction();
try{
$stmt = $dbb->prepare(...);
$stmt->execute();
// stuff
$dbb->commit();
}catch(Exception $e){
// do stuff
$dbb->rollBack();
// Need to undo what we did
$dbu->beginTransaction();
try{
$stmt = $dbu->prepare(...);
$stmt->execute();
// opposite of whatever operation was in the first transaction
$dbu->commit();
}catch(Exception $e){
}
exit();
}
Это беспорядочно и ненадежно, если что-то случится с соединением между двумя основными транзакциями.
Вместо этого я хотел бы вложить вторую транзакцию в первую. Кажется логичным, что я смогу это сделать, потому что $dbu
и $dbb
— это два уникальных объекта PDO, которые указывают на две отдельные базы данных. Это выглядит как:
Блок кода 2
$dbu->beginTransaction();
try{
$stmt = $dbu->prepare(...);
$stmt->execute();
// stuff
$dbb->beginTransaction();
try{
$stmt = $dbb->prepare(...);
$stmt->execute();
// stuff
$dbb->commit();
}catch(Exception $e){
// do stuff
$dbb->rollBack();
$dbu->rollBack(); // Since $dbu was first part of transaction, it needs to be rolled back too
exit();
}
$dbu->commit();
}catch(Exception $e){
// do stuff
$dbu->rollBack();
$dbb->rollBack(); // **THIS IS THE TRICKY LINE!**
exit();
}
Поскольку commit()
для $dbu
вызывается после всей транзакции $dbb
, может возникнуть ситуация, когда $dbb
прошла успешно, а $dbu
не удалось. Если это произойдет, мне нужно отменить то, что было сделано в транзакции $dbb
.
Итак...
Могу ли я вызвать $dbb->rollBack();
(в конце блока кода 2) ПОСЛЕ $dbb->commit();
выполнения? Или я застрял в той же ситуации, что и изначально, когда мне нужно вручную отменить все, что произошло в транзакции $dbb
? Опять же, это не идеально. Если соединение прервется посреди этого, я могу остаться с данными в таблицах $dbb
, которых там быть не должно (поскольку транзакция $dbu
не удалась).
Возможно, я смогу объединить две транзакции в один блок try/catch
?
Блок кода 3
$dbu->beginTransaction();
$dbb->beginTransaction();
try{
$stmt = $dbu->prepare(...);
$stmt->execute();
$stmt2 = $dbb->prepare(...);
$stmt2->execute();
// stuff
$dbu->commit();
$dbb->commit();
}catch(Exception $e){
// do stuff
$dbu->rollBack();
$dbb->rollBack(); // **THIS IS THE TRICKY LINE!**
exit();
}
Но это не сильно отличается от блока кода 2, потому что у нас все еще может быть ситуация, когда $dbu->commit();
выполняется успешно, а $dbb->commit();
терпит неудачу. Если это произойдет, то мы все еще пытаемся вызвать $dbu->rollBack();
после того, как его партнерская фиксация уже обработана.
Если я не могу вызвать rollBack()
после commit()
, есть ли общепринятый метод решения этой проблемы с двумя БД? Что-то столь же эффективное, как rollBack()
, и не требует полной дополнительной транзакции для отмены прежней операции.
ИЗМЕНИТЬ 1
В дополнение к блоку кода 3, могу ли я проверить каждое выполнение по мере их вызова?
Блок кода 4
$dbu->beginTransaction();
$dbb->beginTransaction();
try{
$stmt = $dbu->prepare(...);
if(!$stmt->execute()){
throw new Exeption('something somethign');
}
$stmt2 = $dbb->prepare(...);
if(!$stmt2->execute()){
throw new Exeption('something two');
}
// stuff
$dbu->commit();
$dbb->commit();
}catch(PDOException $e){
// do stuff
$dbu->rollBack();
$dbb->rollBack(); // **THIS IS THE TRICKY LINE!**
exit();
}catch(Exception $e){
// do stuff
$dbu->rollBack();
$dbb->rollBack(); // **THIS IS THE TRICKY LINE!**
exit();
}
Поможет ли это гарантировать, что два утверждения commit
будут иметь наилучшие шансы на успех? Или блок try/catch
автоматически выбрасывает PDOException
до того, как будут вызваны пользовательские блоки? Было бы неплохо иметь простой идентификатор, чтобы узнать, какая транзакция не удалась, в отличие от всего $e->getMessage();
.
setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
, поэтому я думаю, что он даже не увидит мои пользовательские исключения. 21.05.2016prepare
/execute
своим собственным блокомtry
/catch
, чтобы различать их, о чем вы, вероятно, уже догадались. 21.05.2016PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION
), но без блоковtry/catch
? Я предполагаю, что сталкиваюсь с проблемой только в случае сбоя запроса? 22.05.2016try
/catch
, есть две возможности: Либо у вас установлен обработчик исключений (php.net/manual/en/function.set-exception-handler.php), и в этом случае необработанное исключение передается обработчику, или PHP умирает с неперехваченным исключением ... ошибка, которая немедленно остановит выполнение (я не уверен, вызываются ли деструкторы и другие...). Подробнее см. php.net/manual/en/language.exceptions.php. Информация. 24.05.2016