sql >> Base de Datos >  >> RDS >> Mysql

¿Cómo detectar que la transacción ya se ha iniciado?

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

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

Sé que algunos marcos intentan hacerlo, y hacen cosas ridículas como contar cuántas veces has comenzado una transacción, solo resolviéndola cuando has hecho commit o rollback un número equivalente de veces. Pero esto es totalmente falso porque ninguna de sus funciones puede saber si la confirmación o la reversión realmente lo harán, o si están en otra capa de anidamiento.

(¿Puedes decir que he tenido esta discusión varias veces? :-)

Actualización 1: Propel es una biblioteca de acceso a la base de datos de 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 commit/rollback disminuye el contador. A continuación se muestra un extracto de un hilo de la lista de correo donde describo algunos escenarios en los que falla.

Actualización 2: Doctrine DBAL también tiene esta característica. Lo llaman anidamiento de transacciones.

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

Escenario del problema #1

Llamo a commit() , ¿se confirman mis cambios? Si me estoy ejecutando dentro de una "transacción interna", no lo son. El código que administra la transacción externa podría optar por retroceder y mis cambios se descartarían sin mi conocimiento o control.

Por ejemplo:

  1. Modelo A:iniciar transacción
  2. Modelo A:ejecutar algunos cambios
  3. Modelo B:comenzar la transacción (sin operación silenciosa)
  4. Modelo B:ejecutar algunos cambios
  5. Modelo B:compromiso (no operativo silencioso)
  6. Modelo A:retroceder (descarta tanto los cambios del modelo A como los cambios del modelo B)
  7. Modelo B:¿¡WTF!? ¿Qué pasó con mis cambios?

Escenario del problema #2

Una transacción interna retrocede, 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 aún está activa y disponible para ser confirmada. Con su parche, podrían llamar a commit() , y dado que transDepth ahora es 0, establecería silenciosamente $transDepth a -1 y devuelve verdadero, después de no confirmar nada.

Escenario del problema #3

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

Básicamente, tratar de administrar transacciones en la lógica de la aplicación sin permitir que la base de datos lleve la contabilidad es una idea condenada al fracaso. Si tiene un requisito para que dos modelos utilicen el control de transacciones explícito en una solicitud de aplicación, debe abrir dos conexiones de base de datos, una para cada modelo. Luego, cada modelo puede tener su propia transacción activa, que puede confirmarse o revertirse independientemente una de la otra.