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

Я использую Zend_Db для вставки некоторых данных внутри транзакции. Моя функция запускает транзакцию, а затем вызывает другой метод, который также пытается запустить транзакцию и, конечно, не удается (я использую MySQL5). Итак, вопрос - как я могу обнаружить, что транзакция уже была начата? Вот пример кода:

       try {
                    Zend_Registry::get('database')->beginTransaction();

                    $totals = self::calculateTotals($Cart);
                    $PaymentInstrument = new PaymentInstrument;
                    $PaymentInstrument->create();
                    $PaymentInstrument->validate();
                    $PaymentInstrument->save();

                    Zend_Registry::get('database')->commit();
                    return true;

            } catch(Zend_Exception $e) {
                    Bootstrap::$Log->err($e->getMessage());
                    Zend_Registry::get('database')->rollBack();
                    return false;
            }

Внутри PaymentInstrument::create есть еще одна инструкция beginTransaction, которая создает исключение, которое говорит о том, что транзакция уже началась.

11 ответов

Фреймворк не может знать, начали ли вы транзакцию. Вы даже можете использовать $db->query('START TRANSACTION') о котором фреймворк не знает, потому что он не анализирует выполняемые вами операторы SQL.

Дело в том, что ответственность за отслеживание того, начали ли вы транзакцию или нет, лежит на приложении. Это не то, что фреймворк может сделать.

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

(Можете ли вы сказать, что у меня было это обсуждение несколько раз?:-)

редактировать: Propel - это библиотека доступа к базе данных PHP, которая поддерживает концепцию "внутренней транзакции", которая не фиксируется, когда вы ее указываете. Начало транзакции только увеличивает счетчик, а фиксация / откат уменьшает счетчик. Ниже приведен отрывок из цепочки рассылки, где я опишу несколько сценариев, когда он терпит неудачу.


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

Проблемный сценарий № 1

Я звоню commit()мои изменения зафиксированы? Если я работаю внутри "внутренней транзакции", это не так. Код, управляющий внешней транзакцией, может откатиться, и мои изменения будут отклонены без моего ведома или контроля.

Например:

  1. Модель A: начать транзакцию
  2. Модель A: выполнить некоторые изменения
  3. Модель B: начать транзакцию (без звука)
  4. Модель B: выполнить некоторые изменения
  5. Модель B: фиксация (без звука)
  6. Модель A: откат (отменяет как изменения модели A, так и изменения модели B)
  7. Модель B: WTF!? Что случилось с моими изменениями?

Проблемный сценарий № 2

Откат внутренней транзакции может отменить законные изменения, сделанные внешней транзакцией. Когда элемент управления возвращается во внешний код, он считает, что его транзакция все еще активна и доступна для принятия. С вашим патчем они могли бы позвонить commit()и так как transDepth теперь 0, он будет молча установить $transDepth -1 и верните истину, не совершая ничего.

Проблемный сценарий № 3

Если я позвоню commit() или же rollback() когда нет активной транзакции, она устанавливает $transDepth до -1. Следующий beginTransaction() увеличивает уровень до 0, что означает, что транзакция не может быть ни отменена, ни зафиксирована. Последующие звонки commit() просто уменьшит транзакцию до -1 или более, и вы никогда не сможете зафиксировать, пока не сделаете еще один лишний beginTransaction() увеличить уровень снова.

По сути, пытаться управлять транзакциями в логике приложения, не позволяя базе данных вести бухгалтерию, является обреченной идеей. Если для двух моделей требуется использовать явное управление транзакциями в одном запросе приложения, необходимо открыть два соединения с БД, по одному для каждой модели. Тогда каждая модель может иметь свою активную транзакцию, которая может быть зафиксирована или откатана независимо друг от друга.

(см. http://www.nabble.com/Zend-Framework-Db-Table-ORM-td19691776.html)

Сделайте попытку / поймайте: если исключение состоит в том, что транзакция уже началась (на основе кода ошибки или сообщения строки, что угодно), продолжайте. В противном случае, сгенерируйте исключение снова.

Глядя на Zend_Db, а также на адаптеры (версии mysqli и PDO), я не вижу какого-либо приятного способа проверить состояние транзакции. Похоже, в этом есть проблема с ZF - к счастью, с патчем, который скоро выйдет.

В настоящее время, если вы не хотите запускать неофициальный код ZF, в документации MySQL указано, что вы можете SELECT @@autocommit чтобы узнать, находитесь ли вы в данный момент в транзакции (ошибка... не в режиме автоматической фиксации).

Сохраните возвращаемое значение beginTransaction() в Zend_Registry и проверьте его позже.

Для innoDB вы должны быть в состоянии использовать

SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX WHERE TRX_MYSQL_THREAD_ID = CONNECTION_ID();

Эта дискуссия довольно старая. Как уже отмечалось, вы можете сделать это в своем приложении. PHP имеет метод начиная с версии 5 >= 5.3.3, чтобы узнать, находитесь ли вы в середине транзакции. PDP::inTransaction() возвращает true или false. Ссылка http://php.net/manual/en/pdo.intransaction.php

Вы также можете написать свой код следующим образом:

try {
    Zend_Registry::get('database')->beginTransaction();
} 
catch (Exception $e) { }

try {
    $totals = self::calculateTotals($Cart);

    $PaymentInstrument = new PaymentInstrument;
    $PaymentInstrument->create();
    $PaymentInstrument->validate();
    $PaymentInstrument->save();

    Zend_Registry::get('database')->commit();
    return true;
} 
catch (Zend_Exception $e) {
    Bootstrap::$Log->err($e->getMessage());
    Zend_Registry::get('database')->rollBack();
    return false;
}

Я не согласен с оценкой Билла Карвина о том, что отслеживание начатых транзакций - это тупица, хотя мне это слово нравится.

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

Так что я держу счет. Я использую CodeIgniter, который, кажется, делает странные вещи, если я прошу его начать использовать вложенные транзакции БД (например, вызывая его метод trans_start() более одного раза). Другими словами, я не могу просто включить trans_start() в мой обработчик событий, потому что если внешняя функция также использует trans_start(), откаты и коммиты происходят некорректно. Всегда есть вероятность, что я еще не понял, как правильно управлять этими функциями, но я провел много тестов.

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

У меня есть функции-оболочки для методов транзакций CodeIgniter, и эти функции увеличивают / уменьшают счетчик.

function transBegin(){
    //increment our number of levels
    $this->_transBegin += 1;
    //if we are only one level deep, we can create transaction
    if($this->_transBegin ==1) {
        $this->db->trans_begin();
    }
}

function transCommit(){
    if($this->_transBegin == 1) {
        //if we are only one level deep, we can commit transaction
        $this->db->trans_commit();
    }
    //decrement our number of levels
    $this->_transBegin -= 1;

}

function transRollback(){
    if($this->_transBegin == 1) {
        //if we are only one level deep, we can roll back transaction
        $this->db->trans_rollback();
    }
    //decrement our number of levels
    $this->_transBegin -= 1;
}

В моей ситуации это единственный способ проверить существующую транзакцию БД. И это работает. Я бы не сказал, что "Приложение управляет транзакциями БД". Это действительно не соответствует действительности в этой ситуации. Он просто проверяет, запустила ли какая-либо другая часть приложения какие-либо транзакции БД, чтобы избежать создания вложенных транзакций БД.

Используйте Zend Profiler, чтобы видеть начало как текст запроса, и Zend_Db_Prfiler::TRANSACTION как тип запроса без коммита или отката в качестве текста запроса впоследствии. (Предполагая, что в вашем приложении нет запроса -> ("START TRANSACTION") и Zend Profiler включен)

Возможно, вы можете попробовать PDO::inTransaction... возвращает TRUE, если транзакция в данный момент активна, и FALSE, если нет. Я не проверял себя, но, кажется, не плохо!

В PHP с веб-интерфейсом скрипты почти всегда вызываются во время одного веб-запроса. В этом случае вы действительно хотели бы начать транзакцию и зафиксировать ее непосредственно перед завершением сценария. Если что-то пойдет не так, бросьте исключение и откатите все обратно. Как это:

wrapper.php:

try {
   // start transaction
   include("your_script.php");
   // commit transaction
} catch (RollbackException $e) {
   // roll back transaction
}

Ситуация с шардингом становится немного сложнее, когда вы можете открывать несколько соединений. Вы должны добавить их в список соединений, где транзакции должны быть зафиксированы или откатаны в конце скрипта. Однако следует понимать, что в случае шардинга, если у вас нет глобального мьютекса для транзакций, вы не сможете легко добиться истинной изоляции или атомарности параллельных транзакций, поскольку другой скрипт может передавать свои транзакции в шарды во время фиксации. ваша. Однако, вы можете проверить распределенные транзакции MySQL.

Другие вопросы по тегам