2008-11-26 37 views
25

Estoy usando Zend_Db para insertar algunos datos dentro de una transacción. Mi función inicia una transacción y luego llama a otro método que también intenta iniciar una transacción y, por supuesto, falla (estoy usando MySQL5). Entonces, la pregunta es: ¿cómo puedo detectar que la transacción ya se haya iniciado? Aquí es un poco de muestra de código:¿Cómo se detecta que la transacción ya se inició?

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

Dentro PaymentInstrument :: crear hay otra declaración beginTransaction que produce la excepción de que dice que la transacción ya se ha iniciado.

Respuesta

2

Almacena el valor de retorno de beginTransaction() en Zend_Registry, y verifícalo más tarde.

2

Mirando el Zend_Db así como los adaptadores (versiones mysqli y PDO) Realmente no veo una buena manera de verificar el estado de la transacción. Parece haber un ZF issue con respecto a esto, afortunadamente con un parche programado para salir pronto.

Por el momento, si prefiere no ejecutar el código ZF no oficial, el mysqli documentation dice que puede SELECT @@autocommit para averiguar si se encuentra actualmente en una transacción (err ... no en modo de confirmación automática).

+0

parece que esa cuestión se perdió en su programa de seguimiento ... :( – xelurg

+0

Todos los temas ZF dicen "arreglar en la próxima versión menor" hasta que realmente fijos. Espero que tenían una buena razón para hacer eso, porque es bastante engañoso y causa confusión para mucha gente. –

+1

En mi prueba a través del cliente mysql 'SELECT @@ autocommit;' todavía devuelve 1 durante una transacción. – ColinM

4

Intente/atrape: si la excepción es que una transacción ya ha comenzado (basada en el código de error o el mensaje de la cadena, lo que sea), continúe. De lo contrario, lanza la excepción nuevamente.

22

El marco no tiene forma de saber si inició una transacción. Incluso puede usar $db->query('START TRANSACTION') que la infraestructura no conocería porque no analiza las sentencias SQL que ejecuta.

El punto es que es una aplicación la responsabilidad de rastrear si ha iniciado una transacción o no. No es algo que el marco pueda hacer.

Conozco algunos frameworks que tratan de hacerlo, y hago cosas divertidas como contar cuántas veces has comenzado una transacción, solo resolviéndola cuando has completado commit o rollback una cantidad de veces correspondiente. Pero esto es totalmente falso porque ninguna de sus funciones puede saber si el commit o el rollback realmente lo harán, o si están en otra capa de nesting.

(¿se puede decir que he tenido esta discusión un par de veces :-)

edición:?Propel es una biblioteca de acceso a la base de datos PHP que apoya el concepto de la "transacción interna" que no lo hace cometer cuando se lo digas. Comenzar una transacción solo incrementa un contador, y la confirmación/reposición disminuye el contador. A continuación se muestra un extracto de un hilo de la lista de correo donde describo algunos escenarios donde falla.


Nos guste o no, las transacciones son "globales" y no obedecen a la encapsulación orientada a objetos.

escenario Problema # 1

llamo commit(), están comprometidos mis cambios? Si estoy ejecutando dentro de una "transacción interna", no lo están. El código que gestiona la transacción externa podría optar por retrotraerse y mis cambios se descartarían sin mi conocimiento o control.

Por ejemplo:

  1. Modelo A: iniciar la transacción
  2. Modelo A: ejecutar algunos cambios
  3. Modelo B: iniciar la transacción (silencio no-op)
  4. Modelo B: ejecutar algunos cambios
  5. Modelo B: confirmación (silenciosa no operativa)
  6. Modelo A: retrotracción (descarta los cambios del modelo A y los cambios del modelo B)
  7. Modelo B: ¿¡WTF !? ¿Qué pasó con mis cambios?

escenario Problema # 2

Una transacción interior deshace, se podría descartar los cambios legítimos hechas mediante una operación externa. Cuando se devuelve el control al código externo, cree que su transacción todavía está activa y disponible para comprometerse. Con su parche, podrían llamar al commit(), y dado que transDepth ahora es 0, establecería silenciosamente $transDepth en -1 y devolvería verdadero, después de no confirmar nada.

escenario Problema # 3

Si llamo commit() o rollback() cuando no hay transacción activa, se establece la $transDepth a -1. El siguiente beginTransaction() incrementa el nivel a 0, lo que significa que la transacción no se puede retrotraer ni comprometer. Las llamadas posteriores al commit() simplemente disminuirán la transacción a -1 o más, y nunca podrá confirmar hasta que haga otro beginTransaction() superfluo para incrementar el nivel nuevamente.

Básicamente, tratar de administrar las transacciones en la lógica de la aplicación sin permitir que la base de datos haga la contabilidad es una idea condenada. Si tiene un requisito para que dos modelos utilicen control de transacción explícito en una solicitud de aplicación, entonces debe abrir dos conexiones de base de datos, una para cada modelo. Luego, cada modelo puede tener su propia transacción activa, que puede comprometerse o retrotraerse de forma independiente entre sí.

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

+0

es verdad, aunque sin duda sería una característica extremadamente agradable. Me pregunto si algo parecido existe en Hibernate o en cualquier otra capa de persistencia más madura ... – xelurg

+1

Propel tiene esto, pero sigo creyendo que es un diseño falso. Consulte mi edición anterior. –

+0

volviendo a este tema ... JPA, por ejemplo, tiene un concepto de TransactionManager sería c similar oncept ser lógico tener para ZF? – xelurg

0

En PHP orientadas a la web, los scripts son casi siempre se invocan durante una única solicitud web. Lo que realmente le gustaría hacer en ese caso es iniciar una transacción y confirmarla justo antes de que termine el guión. Si algo sale mal, lanza una excepción y revierte todo. De esta manera:

wrapper.php: 

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

La situación es un poco más complejo con sharding, donde se vea obligado a abrir varias conexiones. Debe agregarlos a una lista de conexiones donde las transacciones se deben comprometer o revertir al final del script. Sin embargo, tenga en cuenta que en el caso de fragmentación, a menos que tenga un mutex global en las transacciones, no podrá lograr fácilmente el aislamiento o la atomicidad reales de las transacciones simultáneas porque otra secuencia de comandos podría estar cometiendo sus transacciones a fragmentos mientras se compromete tuya. Sin embargo, es posible que desee comprobar distributed transactions de MySQL.

0

Utilice zend profiler para ver comenzar como texto de consulta y Zend_Db_Prfiler :: TRANSACTION como tipo de consulta sin commit o rollback como texto de consulta después.(Asumiendo que no hay -> query ("iniciar la transacción") y un perfilador de Zend habilitadas en su aplicación)

1

También puede escribir el código según sigue:

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

Para InnoDB debería poder utilizar

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

no estoy de acuerdo con la evaluación del proyecto de ley Karwin que hacer el seguimiento de las transacciones iniciadas es disparatada, aunque me gusta esa palabra.

Tengo una situación en la que tengo funciones de controlador de eventos que pueden ser llamadas por un módulo no escrito por mí. Mis controladores de eventos crean muchos registros en el db. Definitivamente tengo que retroceder si algo no se pasó correctamente o si falta algo o algo va, bueno, cockamamie. No puedo saber si el código del módulo externo que desencadena el controlador de eventos está manejando las transacciones de db, porque el código está escrito por otras personas. No he encontrado una forma de consultar la base de datos para ver si una transacción está en progreso.

Así que TENGO CUENTA. Estoy usando CodeIgniter, que parece hacer cosas extrañas si le pido que empiece a usar transacciones de DNS anidados (por ejemplo, llamar a su método trans_start() más de una vez). En otras palabras, no puedo simplemente incluir trans_start() en mi controlador de eventos, porque si una función externa también está usando trans_start(), las reversiones y confirmaciones no ocurren correctamente. Siempre existe la posibilidad de que aún no me haya dado cuenta de cómo administrar esas funciones correctamente, pero he realizado muchas pruebas.

Todos mis controladores de eventos deben saber si una transacción de transferencia de datos ya ha sido iniciada por otro módulo que realiza la llamada. Si es así, no inicia otra transacción nueva y tampoco respeta ningún rollback o commit. Confía en que si alguna función externa ha iniciado una transacción db, también se encargará de deshacer/comprometer.

Tengo funciones de contenedor para los métodos de transacción de CodeIgniter y estas funciones incrementan/disminuyen un contador.

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

En mi situación, esta es la única manera de verificar si hay una transacción de db existente. Y funciona. No diría que "la aplicación está gestionando transacciones de db". Eso es realmente falso en esta situación. Simplemente, verifica si alguna otra parte de la aplicación ha iniciado transacciones de db, de modo que puede evitar la creación de transacciones db anidadas.

1

Esta discusión es bastante antigua. Como algunos han señalado, puedes hacerlo en tu aplicación. PHP tiene un método desde la versión 5> = 5.3.3 para saber si estás en medio de una transacción. PDP :: inTransaction() devuelve verdadero o falso. Enlace http://php.net/manual/en/pdo.intransaction.php

+0

Quizás la capa de DB del marco no encapsula este método PDO para verificar el estado de la transacción. Vine con la misma respuesta que tú, casi publiqué la mía antes de ver tu respuesta aquí. –

Cuestiones relacionadas