php - remove - strip_tags()
¿Cómo se detecta que la transacción ya se inició? (11)
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í hay un fragmento de código de muestra:
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 de PaymentInstrument :: create hay otra instrucción beginTransaction que produce la excepción que dice que la transacción ya se ha iniciado.
Almacene el valor de retorno de beginTransaction () en Zend_Registry, y verifíquelo más tarde.
El marco no tiene forma de saber si comenzó 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 responsabilidad de la aplicación rastrear si has comenzado una transacción o no. No es algo que el marco pueda hacer.
Sé que algunos frameworks intentan hacerlo, y hacer cockamamie cosas como contar cuántas veces has comenzado una transacción, solo resolviéndola cuando has hecho 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.
(¿Puedes decir que he tenido esta discusión un par de veces? :-)
editar: Propel es una biblioteca de acceso a base de datos PHP que admite el concepto de "transacción interna" que no se compromete cuando se lo indica. 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 de problema # 1
Llamo commit()
, ¿se han confirmado 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:
- Modelo A: comenzar la transacción
- Modelo A: ejecutar algunos cambios
- Modelo B: comenzar la transacción (no-operación silenciosa)
- Modelo B: ejecuta algunos cambios
- Modelo B: commit (no-operación silenciosa)
- Modelo A: reversión (descarta los cambios del modelo A y los cambios del modelo B)
- Modelo B: ¿WTF? ¿Qué pasó con mis cambios?
Escenario de problema # 2
Una transacción interna se retrotrae, podría descartar cambios legítimos realizados por una transacció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 a commit()
, y dado que transDepth ahora es 0, establecería silenciosamente $transDepth
en -1 y devolvería true, luego de no comprometer nada.
Escenario de problema n. ° 3
Si invoco commit()
o rollback()
cuando no hay transacción activa, establece $transDepth
en -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 a commit()
disminuirán la transacción a -1 o más, y nunca podrás comprometer hasta que hagas otra beginTransaction()
superflua 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 )
En PHP orientado a la web, los scripts casi siempre se invocan durante una sola 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. Me gusta esto:
wrapper.php:
try {
// start transaction
include("your_script.php");
// commit transaction
} catch (RollbackException $e) {
// roll back transaction
}
La situación se vuelve un poco más compleja con la fragmentación, donde es posible que esté abriendo 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 verificar las transacciones distribuidas de MySQL.
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
Intente / capture: 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.
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 problema de ZF con respecto a esto, afortunadamente con un parche programado para salir pronto.
Por el momento, si prefiere no ejecutar un código ZF no oficial, la documentación de mysqli dice que puede SELECT @@autocommit
para averiguar si se encuentra actualmente en una transacción (err ... no en el modo de confirmación automática).
No estoy de acuerdo con la evaluación de Bill Karwin de que hacer un seguimiento de las transacciones iniciadas es una cacora, 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 que no he escrito. 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 SI sigo contando. Estoy usando CodeIgniter, que parece hacer cosas extrañas si le pido que empiece a usar transacciones de DNS anidadas (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 necesitan saber si una transacción de transferencia de datos ya ha sido iniciada por otro módulo que llama. 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 forma 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.
Para innoDB deberías poder usar
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX WHERE TRX_MYSQL_THREAD_ID = CONNECTION_ID();
Tal vez puedas probar PDO :: inTransaction ... devuelve TRUE si una transacción está actualmente activa, y FALSE si no. ¡No me he probado a mí mismo, pero parece que no está mal!
También puede escribir su código de la siguiente manera:
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;
}
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. (Suponiendo que no hay -> consulta ("INICIAR TRANSACCIÓN") y Zend Profiler habilitados en su aplicación)