Error when mysqli_rollback does not roll back statements executed from a temporary table

I am trying to perform a series of operations with both temporary tables and the non-temporary machines table.
I want them all to be executed in one transaction.

I am testing that the rollback works correctly by forcing it with throw new Exception(‘force rollback’);

However, the registration is always made in the machines table (it is not reversed as I expect). The result of mysqli_rollback is 1.

That could be happening?

Code:

<?php 
session_start();
include_once("../utils/system_utils.php");
include_once("../models/Maquina.php");
include_once("../models/Temp.php");

set_time_limit(300);

$error      = "";
$id         = null;
// Parametros de respuesta
$success    = 0;
$message="";
$data       = [];

$id_maquina         = post('id_maquina');
$nombre_anterior    = post('nombre_anterior');
$nombre             = post('duplicate_name');
$reuso     = post('reuso');

registrarLog("[utils - duplicar_maquina] - POST: ".json_encode(filter_input_array(INPUT_POST)));

$conexion = db_connect();
mysqli_begin_transaction($conexion);
try {


    $query = "CREATE TEMPORARY TABLE tmp_dup SELECT * from maquinas WHERE id = ?";
    registrarLog("Params: $id_maquina - $query [Maquinas::createMaquinaTemporal]", DEBUG);
    $stmt = mysqli_prepare($conexion, $query);
    mysqli_stmt_bind_param($stmt, 'i', $id_maquina);
    if (!mysqli_stmt_execute($stmt)){
        throw new Exception(gettext('Los datos no han podido ser guardados'));
    }



    $query = "ALTER TABLE tmp_dup drop id";  
    registrarLog("$query [Temp::borrarCampoID]", DEBUG);
    $stmt = mysqli_prepare($conexion, $query);    
    if(!mysqli_stmt_execute($stmt)) {
        throw new Exception(gettext('Los datos no han podido ser guardados'));
    }


        $query = "UPDATE tmp_dup SET nombre = ?, "
                    ."estado = 0, "
                    ."accion = NULL, "
                    ."mensaje = NULL, "
                    ."fecha_estado = NULL, "
                    
                    ."fecha_alta = CURRENT_TIMESTAMP, "
                    ."fecha_actualizacion = NULL, "
                    ."fecha_lectura = NULL, "
                    ."fecha_comm = NULL, ";

        if (!$reuso){
            $query .= "ip = NULL, "
                    ."imei = CONCAT(imei, '2'), ";
        }

        $query .= "fecha_ultima_venta = NULL, "
                    ."version = '0.0.0', "
                    ."initialized = '0', "
                    ."price_changed = '0', "
                    ."unions_changed = '0', "
                    ."tft_changed = '0', "
                    ."lcd_changed = '0', "
                    ."version_changed = '0', "
                    ."coverage = NULL, "
                    ."banda = NULL, "
                    ."operador = NULL";

        registrarLog("Params: $nombre, $query [Temp::borrarCampoID]", DEBUG);
        $stmt = mysqli_prepare($conexion, $query);
        mysqli_stmt_bind_param($stmt, 's', $nombre);
        if(! mysqli_stmt_execute($stmt)){
            throw new Exception(gettext('Los datos no han podido ser guardados'));
        }



        $id = false;        
        $query = 'INSERT INTO maquinas SELECT 0, tmp_dup.* FROM tmp_dup';
        registrarLog("$query [Maquinas::createMaquinaDesdeTemp]", DEBUG);

        $stmt = mysqli_stmt_init($conexion);                
        mysqli_stmt_prepare($stmt, $query);
        if (mysqli_stmt_execute($stmt)){
            $id = mysqli_insert_id($conexion);
        }

        if(!$id){
            registrarLog('ERR - No se ha podido registrar la maquina temporal. Error: '.mysqli_stmt_error($stmt). '- [Maquina::createMaquinaDesdeTemp]');
        }
        else {
            registrarLog('Se ha registrado la maquina temporal. ID = '.$id. '- [Maquina::createMaquinaDesdeTemp]');
        } 

        if(!$id){
            throw new Exception(gettext('Los datos no han podido ser guardados'));
        } 
        

        throw new Exception('fuerzo el rollback');
        mysqli_commit($conexion);

    
} catch (\Exception $e) {
    registrarLogRedFlag("EXC - Se ha producido un error al duplicar la maquina (id = $id_maquina): Revisar posible inconsistencia en los datos.");
    $message = $e->getMessage();
    $rroll = mysqli_rollback($conexion);
    registrarLogRedFlag("rroll: ".$rroll);

}


echo getResponseArray($success, $message, $data);


  • 2

    Add mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); before your mysqli_connect() (or new mysqli()) command, and this will ensure that errors with your SQL queries are reported correctly to PHP automatically (and in your case, will then be caught by your try/catch). That way you don’t need to clutter your script with repetitive code to keep checking errors after mysqli commands (and in any case, you were a long way from covering all of the commands where a failure could occur).

    – 




  • @shingo It was active. When active, a set of sql commands cannot be guaranteed to all execute correctly. Not even running mysqli_begin_transaction at startup right?

    – 

  • 2

    Sure…but in development mode that doesn’t matter, you can show the errors. In a live environment you would redirect all PHP errors to a log file anyway, so the exact SQL error should never show up in that scenario either. And also, in this case at least, you’re catching the exceptions anyway.

    – 




  • 3

    I think your ALTER TABLE is causing an implicit commit, after which there is no active transaction to rollback. Statements That Cause an Implicit Commit

    – 

  • 2

    It does not matter what the autocommit status is, as it is effectively suspended when you explicitly start a transaction, until there is a commit or rollback (explicit or implicit).

    – 




Leave a Comment