Nano Hash - криптовалюты, майнинг, программирование

PHP, MySQL, транзакция PDO. Можно ли использовать rollBack() после вызова commit()?

Я просмотрел ресурсы для 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();.

21.05.2016

Ответы:


1

Вы не можете отменить зафиксированные изменения.

Как и в случае с вашим другим вопросом блок кода 3 это путь. Даже если фиксация может завершиться ошибкой, она не произойдет из-за распространенных ошибок (таких как неправильный синтаксис, нарушение ограничений или что-то еще). Гипотетически весь процесс PHP может быть остановлен прямо между обеими фиксациями, сбросив последнюю, что не позволит вам исправить полученные ошибки в коде. Однако вам придется позаботиться об этих редких исключениях отдельно (например, о резервных копиях), потому что я не вижу эффективного способа обработки их в коде.

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

@ИЗМЕНИТЬ 1

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

Ваш блок кода 4 будет работать, если вы используете режим по умолчанию (не устанавливая явно режим ошибки или устанавливая его на PDO::ERRMODE_SILENT).

21.05.2016
  • Хорошо, это имеет смысл, поскольку сам коммит редко терпит неудачу, поскольку к этому моменту вся работа уже сделана. Я добавил правку в конец вопроса с парой мыслей, чтобы помочь смягчить проблемы, если бы вы могли сообщить мне свои мысли, если это вообще стоит добавить. 21.05.2016
  • Ах, попался. Да, у меня установлено значение setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);, поэтому я думаю, что он даже не увидит мои пользовательские исключения. 21.05.2016
  • Если вы установите режим ошибок для генерации исключений, вам придется окружить каждый оператор prepare/execute своим собственным блоком try/catch, чтобы различать их, о чем вы, вероятно, уже догадались. 21.05.2016
  • Что произойдет, если я запущу запрос с указанным выше атрибутом (например, PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION), но без блоков try/catch? Я предполагаю, что сталкиваюсь с проблемой только в случае сбоя запроса? 22.05.2016
  • Да. Как только возникает ошибка (например, сбой запроса), он генерирует исключение. Если нигде в стеке вызовов нет окружающих try/catch, есть две возможности: Либо у вас установлен обработчик исключений (php.net/manual/en/function.set-exception-handler.php), и в этом случае необработанное исключение передается обработчику, или PHP умирает с неперехваченным исключением ... ошибка, которая немедленно остановит выполнение (я не уверен, вызываются ли деструкторы и другие...). Подробнее см. php.net/manual/en/language.exceptions.php. Информация. 24.05.2016

  • 2

    Невозможно иметь правильную транзакцию между разными подключениями к базе данных.

    Хотя вы можете использовать некоторые неудобные обходные пути, ваша транзакция не будет реальной.

    Таким образом, вы должны либо держать все операции в одной базе данных, либо забыть о транзакциях.

    21.05.2016
  • Я думаю, что я собираюсь оставить его таким же, как Code Block 3/4, и периодически запускать скрипт на задании cron (даже один раз в день более чем достаточно), который сравнивает все таблицы и удаляет любые несоответствия из-за ошибки. Просто потратил пару дней на переключение более 1000 запросов на сгруппированные транзакции, подобные приведенным выше, так что это останется на некоторое время. Возможно, в будущем нам придется изменить схему и макеты или вообще выбрать другую базу данных. 22.05.2016
  • @Birrel Когда вы думаете по-другому, вам обязательно следует взглянуть на CAP-теорему (en.wikipedia .org/wiki/CAP_theorem), поскольку я думаю, что это связано с тем, на что хотел указать Ваш здравый смысл. 24.05.2016
  • Новые материалы

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

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

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

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

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..