Ejemplos de transacciones PHP + MySQL

294

Realmente no he encontrado un ejemplo normal de archivo PHP donde se usan transacciones MySQL. ¿Me puede mostrar un ejemplo simple de eso?

Y una pregunta más. Ya hice mucha programación y no utilicé transacciones. ¿Puedo poner una función PHP o algo header.phpasí si una mysql_queryfalla, entonces las otras también?


Creo que lo he descubierto, ¿es correcto ?:

mysql_query("SET AUTOCOMMIT=0");
mysql_query("START TRANSACTION");

$a1 = mysql_query("INSERT INTO rarara (l_id) VALUES('1')");
$a2 = mysql_query("INSERT INTO rarara (l_id) VALUES('2')");

if ($a1 and $a2) {
    mysql_query("COMMIT");
} else {        
    mysql_query("ROLLBACK");
}
buena noches
fuente
10
Puede usar en mysql_query("BEGIN");lugar de secuenciamysql_query("SET AUTOCOMMIT=0"); mysql_query("START TRANSACTION");
Kirzilla
75
Por favor, no use mysql_*funciones en código nuevo . Ya no se mantienen y están oficialmente en desuso . ¿Ves el cuadro rojo ? Aprenda sobre las declaraciones preparadas y use PDO o MySQLi ; este artículo le ayudará a decidir cuál. Si elige PDO, aquí hay un buen tutorial .
Naftali aka Neal
66
¿"Mysql_query (" SET AUTOCOMMIT = 0 ");" establecer todas las conexiones para esperar la función de confirmación o es solo para su conexión relacionada?
Hamid
1
@Neal, en realidad, mysqlmuere a pesar de estar en desuso, estará disponible en PECL para siempre.
Pacerier
2
@Pacerier Las cosas que quedan en desuso no "mueren". Se mantienen oficialmente para el software heredado, pero dejan de ser mantenidos y eliminados de las prácticas recomendadas para el nuevo software. El hecho sigue siendo, no usemysql
taylorcressy

Respuestas:

325

La idea que generalmente uso cuando trabajo con transacciones se ve así (semi-pseudocódigo) :

try {
    // First of all, let's begin a transaction
    $db->beginTransaction();

    // A set of queries; if one fails, an exception should be thrown
    $db->query('first query');
    $db->query('second query');
    $db->query('third query');

    // If we arrive here, it means that no exception was thrown
    // i.e. no query has failed, and we can commit the transaction
    $db->commit();
} catch (Exception $e) {
    // An exception has been thrown
    // We must rollback the transaction
    $db->rollback();
}


Tenga en cuenta que, con esta idea, si una consulta falla, se debe lanzar una excepción:

  • PDO puede hacer eso, dependiendo de cómo lo configure
  • de lo contrario, con alguna otra API, es posible que deba probar el resultado de la función utilizada para ejecutar una consulta y lanzar una excepción usted mismo.


Desafortunadamente, no hay magia involucrada. No puede simplemente colocar una instrucción en algún lugar y hacer que las transacciones se realicen automáticamente: aún debe especificar qué grupo de consultas debe ejecutarse en una transacción.

Por ejemplo, con bastante frecuencia tendrá un par de consultas antes de la transacción (antes de begin) y otro par de consultas después de la transacción (después de cualquiera commito rollback) y querrá que esas consultas se ejecuten sin importar lo que sucedió (o no) en la transacción.

Pascal MARTIN
fuente
35
Tenga cuidado si está realizando operaciones que pueden arrojar excepciones que no sean db. Si es así, una excepción de una declaración que no sea db puede causar una reversión sin darse cuenta (incluso si todas las llamadas db son exitosas). Normalmente, pensaría que retroceder es una buena idea incluso si el error no estaba en el lado de la base de datos, pero hay veces que el código de terceros / no crítico puede causar excepciones no tan importantes, y aún así desea continuar con la transacción.
Halil Özgür
66
¿Cuál es el $dbtipo aquí? mysqli?
Jake
3
@Jake Vea mi respuesta para un ejemplo que usa mysqli (similar en estilo al enfoque de Pascal).
EleventyOne
2
se puede modificar fácilmente para capturar PDOExceptione incluso verificar valores de excepción si es necesario. us2.php.net/PDOException
Yamiko
1
$ db es el objeto PDO (conexión). Ref: php.net/manual/en/pdo.connections.php
Fil
110

Creo que lo he descubierto, ¿es correcto ?:

mysql_query("START TRANSACTION");

$a1 = mysql_query("INSERT INTO rarara (l_id) VALUES('1')");
$a2 = mysql_query("INSERT INTO rarara (l_id) VALUES('2')");

if ($a1 and $a2) {
    mysql_query("COMMIT");
} else {        
    mysql_query("ROLLBACK");
}
buena noches
fuente
26
no es necesario configurar autocommit = 0. las transacciones siempre funcionan de esa manera.
bgcode
2
@babonk: ¿no está seguro de que este sea el caso con InnoDB?
buggedcom
66
Creo que una vez que comienza una transacción, funciona como si AUTOCOMMIT = 0
bgcode
44
@babonk tiene razón. Una vez que se inicia una transacción, AUTOCOMMIT = 0 se está configurando implícitamente y después de que la transacción finaliza, ya sea por confirmación o reversión, MySql restablece el valor de AUTOCOMMIT que se usó antes de comenzar la transacción. NOTA: NO debe establecer AUTOCOMMIT = 0, porque después de confirmar los cambios si decide insertar / actualizar otra fila, debe confirmarlo explícitamente.
eroteev
44
¡La tienda de motores debería ser InnoDB, no MyISAM!
javad
39
<?php

// trans.php
function begin(){
    mysql_query("BEGIN");
}

function commit(){
    mysql_query("COMMIT");
}

function rollback(){
    mysql_query("ROLLBACK");
}

mysql_connect("localhost","Dude1", "SuperSecret") or die(mysql_error());

mysql_select_db("bedrock") or die(mysql_error());

$query = "INSERT INTO employee (ssn,name,phone) values ('123-45-6789','Matt','1-800-555-1212')";

begin(); // transaction begins

$result = mysql_query($query);

if(!$result){
    rollback(); // transaction rolls back
    echo "transaction rolled back";
    exit;
}else{
    commit(); // transaction is committed
    echo "Database transaction was successful";
}

?>
Alex Gedzberg
fuente
Para una pregunta amplia y de alto perfil como esta, sería genial si las respuestas también lo reflejaran. Su código de muestra es excelente, pero ¿puede elaborar más? Explicar sobre las transacciones, ¿por qué, cuándo y dónde? Finalmente, vincule el código con su explicación.
Dennis Haarbrink
3
Bienvenido a StackOverflow. Siempre escriba un texto descriptivo para su respuesta.
Adrian Heine
66
lo siento, soy un principiante, y mi mal inglés, es un examen de código muy fácil - para principiantes - commit () rollback () begin () poner en la clase DB (por ejemplo), $ query - no una vez - tal vez $ query0 $ query1 - luego chek ellos - yo uso este código, esto es muy fácil de entender =)
Gedzberg Alex
20
Sus comentarios hacen que el ejemplo sea bastante claro. Un buen código no debería necesitar describir texto. También la pregunta pide un ejemplo simple. Me gusta esta respuesta
Ninguno
@GedzbergAlex para una sola consulta no hay necesidad de transacción, simplemente confunde acerca de la transacción. ¿Hay alguna razón para usar la transacción para una sola consulta?
ɥʇɹɐʞıɥʇɹɐʞ ouɐɯ
35

Como este es el primer resultado en Google para la "transacción php mysql", pensé en agregar una respuesta que explícitamente demuestre cómo hacer esto con mysqli (como el autor original quería ejemplos). Aquí hay un ejemplo simplificado de transacciones con PHP / mysqli:

// let's pretend that a user wants to create a new "group". we will do so
// while at the same time creating a "membership" for the group which
// consists solely of the user themselves (at first). accordingly, the group
// and membership records should be created together, or not at all.
// this sounds like a job for: TRANSACTIONS! (*cue music*)

$group_name = "The Thursday Thumpers";
$member_name = "EleventyOne";
$conn = new mysqli($db_host,$db_user,$db_passwd,$db_name); // error-check this

// note: this is meant for InnoDB tables. won't work with MyISAM tables.

try {

    $conn->autocommit(FALSE); // i.e., start transaction

    // assume that the TABLE groups has an auto_increment id field
    $query = "INSERT INTO groups (name) ";
    $query .= "VALUES ('$group_name')";
    $result = $conn->query($query);
    if ( !$result ) {
        $result->free();
        throw new Exception($conn->error);
    }

    $group_id = $conn->insert_id; // last auto_inc id from *this* connection

    $query = "INSERT INTO group_membership (group_id,name) ";
    $query .= "VALUES ('$group_id','$member_name')";
    $result = $conn->query($query);
    if ( !$result ) {
        $result->free();
        throw new Exception($conn->error);
    }

    // our SQL queries have been successful. commit them
    // and go back to non-transaction mode.

    $conn->commit();
    $conn->autocommit(TRUE); // i.e., end transaction
}
catch ( Exception $e ) {

    // before rolling back the transaction, you'd want
    // to make sure that the exception was db-related
    $conn->rollback(); 
    $conn->autocommit(TRUE); // i.e., end transaction   
}

Además, tenga en cuenta que PHP 5.5 tiene un nuevo método mysqli :: begin_transaction . Sin embargo, esto no ha sido documentado aún por el equipo de PHP, y todavía estoy atascado en PHP 5.3, por lo que no puedo comentarlo.

Undécimo
fuente
2
En una nota relacionada, acabo de descubrir que si está trabajando con tablas InnoDB, ES posible bloquear / desbloquear tablas cuando se usa el enfoque autocomitt () para las transacciones, pero NO es posible cuando se usa el enfoque begin_transaction (): MySQL documentación
EleventyOne
+1 para un ejemplo detallado (y comentado) con código mysqli real. Gracias por esto. Y su punto sobre el bloqueo / transacciones es muy interesante.
a.real.human.being
1
¿"Autocommit (FALSE)" afectará a otra conexión en la misma base de datos / tabla? Quiero decir, si abrimos dos páginas, una de ellas establece su conexión en "autocommit (FALSE)" pero la otra dejó la función de autocommit, ¿espera la función de confirmación o no? Quiero saber si la confirmación automática es un atributo para las conexiones y no para la base de datos / tabla. Gracias
Hamid
2
@Hamid $conn->autocommit(FALSE), en el ejemplo anterior, solo afecta a la conexión individual, no tiene ningún efecto en ninguna otra conexión a la base de datos.
EleventyOne
1
NOTA: en lugar de if (!result), debería hacerlo if (result === false), si la consulta es capaz de devolver un resultado válido que se evaluaría como falso o cero.
ToolmakerSteve
10

Compruebe qué motor de almacenamiento está utilizando. Si es MyISAM, entonces Transaction('COMMIT','ROLLBACK')no será compatible porque solo el motor de almacenamiento InnoDB, no MyISAM, admite transacciones.

dinesh
fuente
7

Al usar la conexión PDO:

$pdo = new PDO('mysql:host=localhost;dbname=mydb;charset=utf8', $user, $pass, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // this is important
]);

A menudo uso el siguiente código para la gestión de transacciones:

function transaction(Closure $callback)
{
    global $pdo; // let's assume our PDO connection is in a global var

    // start the transaction outside of the try block, because
    // you don't want to rollback a transaction that failed to start
    $pdo->beginTransaction(); 
    try
    {
        $callback();
        $pdo->commit(); 
    }
    catch (Exception $e) // it's better to replace this with Throwable on PHP 7+
    {
        $pdo->rollBack();
        throw $e; // we still have to complain about the exception
    }
}

Ejemplo de uso:

transaction(function()
{
    global $pdo;

    $pdo->query('first query');
    $pdo->query('second query');
    $pdo->query('third query');
});

De esta manera, el código de gestión de transacciones no se duplica en todo el proyecto. Lo cual es bueno, porque, a juzgar por otras respuestas relacionadas con PDO en este hilo, es fácil cometer errores en él. Los más comunes son olvidar volver a lanzar la excepción y comenzar la transacción dentro del trybloque.

Danila Piatov
fuente
5

Hice una función para obtener un vector de consultas y hacer una transacción, tal vez alguien lo encuentre útil:

function transaction ($con, $Q){
        mysqli_query($con, "START TRANSACTION");

        for ($i = 0; $i < count ($Q); $i++){
            if (!mysqli_query ($con, $Q[$i])){
                echo 'Error! Info: <' . mysqli_error ($con) . '> Query: <' . $Q[$i] . '>';
                break;
            }   
        }

        if ($i == count ($Q)){
            mysqli_query($con, "COMMIT");
            return 1;
        }
        else {
            mysqli_query($con, "ROLLBACK");
            return 0;
        }
    }
Marco
fuente
3

Tenía esto, pero no estoy seguro si esto es correcto. Podría probar esto también.

mysql_query("START TRANSACTION");
$flag = true;
$query = "INSERT INTO testing (myid) VALUES ('test')";

$query2 = "INSERT INTO testing2 (myid2) VALUES ('test2')";

$result = mysql_query($query) or trigger_error(mysql_error(), E_USER_ERROR);
if (!$result) {
$flag = false;
}

$result = mysql_query($query2) or trigger_error(mysql_error(), E_USER_ERROR);
if (!$result) {
$flag = false;
}

if ($flag) {
mysql_query("COMMIT");
} else {        
mysql_query("ROLLBACK");
}

Idea de aquí: http://www.phpknowhow.com/mysql/transactions/

efecto de nodo
fuente
Código incorrecto trigger_error devolverá verdadero (a menos que haya arruinado su llamada), por lo que $ result siempre será verdadero, por lo que este código perderá cualquier consulta fallida y siempre intentará confirmar. Igualmente problemático, está usando el viejo en desuso mysql_query, en lugar de usarlo mysqli, aunque se vincula a un tutorial que lo usa mysqli. En mi humilde opinión, debe eliminar este mal ejemplo, o reemplazarlo para usar el código como está escrito en el tutorial phpknowhow.
ToolmakerSteve
2

Un ejemplo más de estilo de procedimiento con mysqli_multi_query, asume que $queryestá lleno de declaraciones separadas por punto y coma.

mysqli_begin_transaction ($link);

for (mysqli_multi_query ($link, $query);
    mysqli_more_results ($link);
    mysqli_next_result ($link) );

! mysqli_errno ($link) ?
    mysqli_commit ($link) : mysqli_rollback ($link);

fuente
1
Código algo extraño, pero al menos sugiere el uso de mysqli_multi_query. Cualquier persona interesada en eso debería buscar en Google en otro lugar por ejemplo más limpio.
ToolmakerSteve