Подтвердить что ты не робот

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

Я использую 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, который создает исключение, в котором говорится, что транзакция уже запущена.

4b9b3361

Ответ 1

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

Дело в том, что это ответственность приложения, чтобы отслеживать, начали ли вы транзакцию или нет. Это не то, что может сделать структура.

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

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

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


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

Сценарий проблемы № 1

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

Например:

  • Модель A: начать транзакцию
  • Модель A: выполните некоторые изменения.
  • Модель B: начать транзакцию (тихий no-op)
  • Модель B: выполните некоторые изменения.
  • Модель B: commit (silent no-op)
  • Модель A: откат (отбрасывает изменения модели A и изменения модели B)
  • Модель 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)

Ответ 2

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

Ответ 3

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

Ответ 4

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

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

Ответ 5

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

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

Ответ 6

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

Ответ 7

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

wrapper.php:

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

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

Ответ 8

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

Ответ 9

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

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;
}

Ответ 10

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

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

Итак, я продолжаю считать. Я использую CodeIgniter, который, кажется, делает странные вещи, если я попрошу его начать использовать вложенные транзакции db (например, вызывая его метод 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;
}

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