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

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

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

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

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

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

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


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

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

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

Например:

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

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

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

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

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

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

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

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

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

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

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

Для 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

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

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

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

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

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

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

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

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