Вызов отката SQL на PHP исключение

0

Рассмотрим следующий сценарий:

  • Я открываю новое подключение к MySQL, используя расширение mysqli
  • Я начинаю транзакцию
  • Я выполняю некоторые запросы в базе данных MySQL и некоторых вычислениях php
  • Либо MySQL, либо php могут бросать некоторые исключения
  • Я совершаю транзакцию

(псевдо) код:

$sql = new mysqli();
...
{ //some scope
    $sql->query("START TRANSACTION");
    ... 
    if (something)
        throw Exception("Something went wrong");
    ...
    $sql->query("COMMIT"); // or $sql->commit()
} //leave scope either normally or through exception
...
$sql->query( ... ); // other stuff not in transaction

Если все вышеперечисленное находится в каком-то большом блоке try catch (или, возможно, даже исключение не реализовано), транзакция останется незавершенной. Если я затем попытаюсь выполнить дополнительную работу над базой данных, эти действия будут принадлежать транзакции.

Мой вопрос: есть ли какой-то возможный механизм, который позволил бы мне автоматически отправлять $sql->rollback() когда я покидаю область создания транзакций ненормальным способом?


Некоторые примечания:

  • mysqli::autocommit - это не то же самое, что транзакция. Это просто настройка автоматического включения или выключения. Эквивалент MySQL SET autocommit.
  • mysqli::begin_transaction не документирован, и я не знаю, каково его точное поведение. Будет ли он ссылаться на rollback когда объект mysqli умирает, например?
  • 2
    Если вы никогда не делаете коммит, то по сути он возвращается через заданный промежуток времени с помощью mysql. По сути, это никогда не совершается.
  • 0
    Проблема может быть в некоторых других запросах, выполняемых для объекта $sql которые могут быть вызваны после обработки исключения. На данный момент я не знаю, нахожусь ли я в режиме транзакции или нет.
Теги:
mysqli

4 ответа

0
Лучший ответ

Вы не можете автоматически откатываться от исключения, но с небольшим кодом вы можете делать то, что хотите. Даже если вы уже находитесь в блоке try-catch вы можете вложить их в свою секцию транзакций, например:

try {
    $sql->query("START TRANSACTION");
    ... 
    if (something)
        throw new PossiblyCustomException("Something went wrong");
    ...
    $sql->query("COMMIT"); 
} catch (Exception $e) { //catch all exceptions
    $sql->query("ROLLBACK");
    throw $e; //rethrow the very same exception object
}

Даже если вы используете наиболее распространенное catch Exception, известен фактический тип исключения. Когда вы заново свернете, его еще можно поймать PossiblyCustomException позже. Таким образом, все управление, которое у вас уже есть, остается незатронутым этим новым блоком try-catch.

1

Команда mysqli::begin_transaction такая же (объектно-ориентированная) как ваш $sql->query("START TRANSACTION"); ;

Не существует способа автоматического отката при исключении.

Вы можете только приходить, если все имеет успех. Тогда это будет "автоматический откат", если нет. Но таким образом, у вас возникнут проблемы очень скоро, если у вас будет больше одной фиксации.

Таким образом, ваш текущий код уже очень хорош. Я сделал бы это полный путь ООП:

$sql->begin_transaction();
try {
   $sql->query('DO SOMETHING');
   if(!true) {
       throw new \Exception("Something went wrong");
   }
   $sql->commit();
}
catch (\Exception exception) {
   $sql->rollback();
}

Вы также можете написать свое собственное исключение:

class SqlAutoRollbackException extends \Exception {
    function __construct($msg, Sql $sql) {
        $sql->rollback();
        parent::__construct($msg);
    }
}

Но вам все равно нужно поймать его.

0

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

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

function main() {
  global $commit_flag = false;
  start_transaction();
  doEverything();

  if ($commit_flag) {
    commit_transaction();
  } else {
    rollback_transaction();
  }
}

function doEverything() {
  global $commit_flag;
  try {
    /* do some stuff that may cause errors */

    $commit_flag = true;
  } catch { ... }
}
0

Вы можете обрабатывать исключения mysql, используя синтаксис DECLARE... HANDLER, но я не считаю нужным пытаться обрабатывать их через PHP!

BEGIN
        DECLARE EXIT HANDLER FOR SQLEXCEPTION  ROLLBACK;
        START TRANSACTION;
          INSERT INTO Users ( user_id ) VALUES('1');
          INSERT INTO Users ( user_id ) VALUES('2');
        COMMIT;
END;

Более подробную информацию можно найти здесь: http://dev.mysql.com/doc/refman/5.5/en/declare-handler.html

Ещё вопросы

Сообщество Overcoder
Наверх
Меню