Exemples de transactions PHP + MySQL

294

Je n'ai vraiment pas trouvé d'exemple normal de fichier PHP où les transactions MySQL sont utilisées. Pouvez-vous me montrer un exemple simple de cela?

Et encore une question. J'ai déjà fait beaucoup de programmation et n'ai pas utilisé de transactions. Puis-je mettre une fonction PHP ou quelque chose dans header.phpce cas si l'un mysql_queryéchoue, les autres échouent aussi?


Je pense que je l'ai compris, est-ce vrai?:

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");
}
Bonsoir
la source
10
Vous pouvez utiliser à la mysql_query("BEGIN");place de la séquencemysql_query("SET AUTOCOMMIT=0"); mysql_query("START TRANSACTION");
Kirzilla
75
Veuillez ne pas utiliser de mysql_*fonctions dans le nouveau code . Ils ne sont plus entretenus et sont officiellement obsolètes . Vous voyez la boîte rouge ? Découvrezplutôt les instructions préparées et utilisez PDO ou MySQLi - cet article vous aidera à décider lesquelles. Si vous choisissez PDO, voici un bon tutoriel .
Naftali aka Neal
6
Est-ce que "mysql_query (" SET AUTOCOMMIT = 0 ");" définir toutes les connexions pour attendre la fonction de validation ou c'est juste pour sa connexion associée?
Hamid
1
@Neal, En fait mysqlwun die malgré sa dépréciation, il sera disponible dans PECL pour toujours.
Pacerier
2
@Pacerier Les choses obsolètes ne "meurent" pas. Ils sont officiellement détenus pour les logiciels hérités, mais cessent d'être maintenus et supprimés de toute pratique recommandée pour les nouveaux logiciels. Le fait demeure, ne l'utilisez pasmysql
taylorcressy

Réponses:

325

L'idée que j'utilise généralement lorsque je travaille avec des transactions ressemble à ceci (semi-pseudo-code) :

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


Notez qu'avec cette idée, si une requête échoue, une exception doit être levée:

  • PDO peut le faire, selon la façon dont vous le configurez
  • sinon, avec une autre API, vous devrez peut-être tester le résultat de la fonction utilisée pour exécuter une requête et lever une exception vous-même.


Malheureusement, il n'y a pas de magie impliquée. Vous ne pouvez pas simplement placer une instruction quelque part et effectuer des transactions automatiquement: vous devez encore spécifier quel groupe de requêtes doit être exécuté dans une transaction.

Par exemple, très souvent, vous aurez quelques requêtes avant la transaction (avant le begin) et un autre couple de requêtes après la transaction (après ou commitou rollback) et vous voudrez que ces requêtes soient exécutées, peu importe ce qui s'est passé (ou non) dans la transaction.

Pascal MARTIN
la source
35
Soyez prudent si vous effectuez des opérations susceptibles de générer des exceptions autres que celles de la base de données. Si tel est le cas, une exception d'une instruction non-db peut provoquer une restauration par inadvertance (même si tous les appels db sont réussis). Normalement, vous penseriez que la restauration est une bonne idée même si l'erreur n'était pas du côté de la base de données, mais il arrive que du code tiers / non critique puisse provoquer des exceptions moins importantes, et vous souhaitez toujours continuer avec la transaction.
Halil Özgür
6
Quel est le $dbtype ici? mysqli?
Jake
3
@Jake Voir ma réponse pour un exemple qui utilise mysqli (similaire dans le style à l'approche de Pascal).
EleventyOne
2
il peut facilement être modifié pour intercepter PDOExceptionet même vérifier les valeurs d'exception si nécessaire. us2.php.net/PDOException
Yamiko
1
$ db est l'objet PDO (connexion). Réf: php.net/manual/en/pdo.connections.php
Fil
110

Je pense que je l'ai compris, est-ce vrai?:

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");
}
Bonsoir
la source
26
pas besoin de définir autocommit = 0. les transactions fonctionnent toujours de cette façon.
bgcode
2
@babonk - pas sûr que ce soit le cas avec InnoDB?
buggedcom
6
Je pense qu'une fois que vous démarrez une transaction, cela fonctionne comme si AUTOCOMMIT = 0
bgcode
4
@babonk a raison. Une fois qu'une transaction est lancée, AUTOCOMMIT = 0 est implicitement définie et une fois la transaction terminée par validation ou restauration, MySql redéfinit la valeur AUTOCOMMIT qui a été utilisée avant de démarrer la transaction. REMARQUE: vous ne devez PAS définir AUTOCOMMIT = 0, car après avoir validé les modifications si vous décidez d'insérer / mettre à jour une autre ligne, vous devez la valider explicitement.
eroteev
4
Le magasin de moteurs devrait être InnoDB, pas 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";
}

?>
Gedzberg Alex
la source
Pour une question large et très médiatisée comme celle-ci, ce serait bien si les réponses reflétaient également cela. Votre exemple de code est excellent, mais pouvez-vous en dire plus? Expliquez les transactions, pourquoi, quand et où? Enfin, liez le code avec votre explication.
Dennis Haarbrink
3
Bienvenue sur StackOverflow. Veuillez toujours écrire du texte descriptif dans votre réponse.
Adrian Heine
6
désolé im begginer, et mon mauvais anglais, son examen très simple du code - pour les débutants - commit () rollback () begin () mis dans la classe DB (par exemple), $ query - pas une fois - peut-être $ query0 $ query1 - puis les chek - j'utilise ce code, c'est très facile à comprendre =)
Gedzberg Alex
20
Ses commentaires rendent l'exemple assez clair. Un bon code ne devrait pas avoir besoin de décrire le texte. La question demande également un exemple simple. J'aime cette réponse.
Aucun
@GedzbergAlex pour une seule requête, il n'y a pas besoin de transaction, il est simplement confus à propos de la transaction. Y a-t-il une raison d'utiliser la transaction pour une seule requête?
ʞɔıɥʇɹɐʞ ouɐɯ
35

Comme c'est le premier résultat sur google pour "php mysql transaction", j'ai pensé ajouter une réponse qui montre explicitement comment faire cela avec mysqli (comme l'auteur original voulait des exemples). Voici un exemple simplifié de transactions avec 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   
}

Gardez également à l'esprit que PHP 5.5 a une nouvelle méthode mysqli :: begin_transaction . Cependant, cela n'a pas encore été documenté par l'équipe PHP, et je suis toujours bloqué en PHP 5.3, donc je ne peux pas en parler.

EleventyOne
la source
2
Sur une note connexe, je viens de découvrir que si vous travaillez avec des tables InnoDB, il est possible de verrouiller / déverrouiller des tables lors de l'utilisation de l'approche autocomitt () pour les transactions, mais ce n'est PAS possible lors de l'utilisation de l'approche begin_transaction (): MySQL documentation
EleventyOne
+1 pour un exemple détaillé (et commenté) avec le code mysqli réel. Merci pour cela. Et votre point sur le verrouillage / les transactions est vraiment très intéressant.
a.real.human.being
1
"Autocommit (FALSE)" affectera-t-il à une autre connexion dans la même base de données / table? Je veux dire que si nous ouvrons deux pages que l'une d'entre elles définit sa connexion sur "autocommit (FALSE)" mais qu'une autre quitte la fonction autocommit, attend-elle ou non la fonction commit? Je veux savoir si la validation automatique est un attribut pour les connexions et non pour la base de données / table. Merci
Hamid
2
@Hamid $conn->autocommit(FALSE), dans l'exemple ci-dessus, n'affecte que la connexion individuelle - il n'a aucun effet sur les autres connexions à la base de données.
EleventyOne
1
REMARQUE: au lieu de if (!result), devrait le faire if (result === false), si la requête est capable de renvoyer un résultat valide qui serait évalué à faux ou à zéro.
ToolmakerSteve
10

Veuillez vérifier le moteur de stockage que vous utilisez. S'il s'agit de MyISAM, alors Transaction('COMMIT','ROLLBACK')il ne sera pas pris en charge car seul le moteur de stockage InnoDB, et non MyISAM, prend en charge les transactions.

dîner
la source
7

Lors de l'utilisation d'une connexion PDO:

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

J'utilise souvent le code suivant pour la gestion des transactions:

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

Exemple d'utilisation:

transaction(function()
{
    global $pdo;

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

De cette façon, le code de gestion des transactions n'est pas dupliqué dans le projet. Ce qui est une bonne chose, car, à en juger par les autres réponses PDO de ce fil, il est facile de s'y tromper. Les plus courants oublient de relancer l'exception et démarrent la transaction à l'intérieur du trybloc.

Danila Piatov
la source
5

J'ai fait une fonction pour obtenir un vecteur de requêtes et faire une transaction, peut-être que quelqu'un le trouvera utile:

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
la source
3

J'ai eu cela, mais je ne sais pas si c'est correct. Pourrait également essayer cela.

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

Idée d'ici: http://www.phpknowhow.com/mysql/transactions/

nodeffect
la source
Code incorrect. trigger_error retournera true (sauf si vous avez foiré votre appel), donc $ result sera toujours vrai, donc ce code manquera toute requête échouée et tentera toujours de valider. Tout aussi troublant, vous utilisez l'ancien obsolète mysql_query, au lieu d'utiliser mysqli, même si vous liez à un didacticiel qui utilise mysqli. À mon humble avis, vous devez soit supprimer ce mauvais exemple, soit le remplacer pour utiliser le code tel qu'écrit dans le tutoriel phpknowhow.
ToolmakerSteve
2

Un autre exemple de style procédural avec mysqli_multi_query, suppose que $queryest rempli d'instructions séparées par des points-virgules.

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

la source
1
Code quelque peu étrange, mais au moins il suggère l'utilisation de mysqli_multi_query. Toute personne intéressée par cela devrait google ailleurs pour un exemple plus propre.
ToolmakerSteve