Как обнаружить, что транзакция уже началась? - PullRequest
27 голосов
/ 26 ноября 2008

Я использую 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 ]

25 голосов
/ 26 ноября 2008

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

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

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

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

edit: 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 и вернул бы true, если ничего не фиксировать.

Сценарий проблемы # 3

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

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

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

4 голосов
/ 26 ноября 2008

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

2 голосов
/ 26 ноября 2008

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

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

2 голосов
/ 26 ноября 2008

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

1 голос
/ 08 июня 2017

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

1 голос
/ 05 июля 2015

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

SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX WHERE TRX_MYSQL_THREAD_ID = CONNECTION_ID();
1 голос
/ 14 марта 2015

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

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;
}
0 голосов
/ 14 марта 2018

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

0 голосов
/ 01 апреля 2017

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

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

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

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

У меня есть функции-оболочки для методов транзакций 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;
}

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

0 голосов
/ 15 мая 2014

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

...