Esempi di transazioni PHP + MySQL


294

Non ho trovato il normale esempio di file PHP in cui vengono utilizzate le transazioni MySQL. Puoi mostrarmi un semplice esempio di questo?

E un'altra domanda. Ho già fatto molta programmazione e non ho usato le transazioni. Posso mettere una funzione PHP o qualcosa del genere header.phpse uno mysql_queryfallisce, anche gli altri falliscono?


Penso di averlo capito, vero ?:

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

10
Puoi usare al mysql_query("BEGIN");posto della sequenzamysql_query("SET AUTOCOMMIT=0"); mysql_query("START TRANSACTION");
Kirzilla il


6
Fa "mysql_query (" SET AUTOCOMMIT = 0 ");" impostare tutte le connessioni in attesa della funzione di commit o è solo per la relativa connessione?
Hamid,

1
@Neal, in realtà mysqlmoriremo nonostante sia deprecato, sarà disponibile in PECL per sempre.
Pacerier,

2
@Pacerier Le cose che diventano deprecate non "muoiono". Sono tenuti ufficialmente per software legacy ma cessano di essere mantenuti e colpiti da qualsiasi pratica raccomandata per il nuovo software. Resta il fatto, non usaremysql
taylorcressy,

Risposte:


325

L'idea che generalmente utilizzo quando lavoro con le transazioni è simile a questo (semi-pseudo-codice) :

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


Nota che, con questa idea, se una query fallisce, deve essere generata un'eccezione:

  • Il DOP può farlo, a seconda di come lo si configura
  • altrimenti, con qualche altra API, potrebbe essere necessario testare il risultato della funzione utilizzata per eseguire una query e generare un'eccezione.


Sfortunatamente, non c'è magia. Non puoi semplicemente inserire un'istruzione da qualche parte e fare eseguire automaticamente le transazioni: devi ancora specificare quale gruppo di query deve essere eseguito in una transazione.

Ad esempio, abbastanza spesso avrai un paio di query prima della transazione (prima del begin) e un'altra coppia di query dopo la transazione (dopo uno commito entrambi rollback) e vorrai che tali query vengano eseguite indipendentemente da ciò che è accaduto (o meno) in la transazione.


35
Fare attenzione se si stanno eseguendo operazioni che potrebbero generare eccezioni diverse da quelle di db. In tal caso, un'eccezione da un'istruzione non db può causare un rollback involontario (anche se tutte le chiamate db hanno esito positivo). Normalmente, penseresti che il rollback sia una buona idea anche se l'errore non era sul lato db, ma ci sono volte che codice di terze parti / non critico può causare eccezioni non così importanti e vuoi comunque continuare con la transazione.
Halil Özgür,

6
Qual è il $dbtipo qui? mysqli?
Jake,

3
@Jake Vedi la mia risposta per un esempio che usa mysqli (simile nello stile all'approccio di Pascal).
EleventyOne,

2
può essere facilmente modificato per rilevare PDOExceptione persino controllare i valori delle eccezioni, se necessario. us2.php.net/PDOException
Yamiko

1
$ db è l'oggetto PDO (connessione). Rif: php.net/manual/en/pdo.connections.php
Fil

110

Penso di averlo capito, vero ?:

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

26
non è necessario impostare autocommit = 0. le transazioni funzionano sempre in questo modo.
bgcode

2
@babonk - non sei sicuro che sia il caso di InnoDB?
buggedcom,

6
Penso che una volta
avviata

4
@babonk ha ragione. Una volta avviata una transazione, AUTOCOMMIT = 0 viene impostato in modo implicito e dopo che la transazione termina con commit o rollback, MySql ripristina il valore AUTOCOMMIT che è stato utilizzato prima di iniziare la transazione. NOTA: NON impostare AUTOCOMMIT = 0, poiché dopo aver eseguito le modifiche se si decide di inserire / aggiornare un'altra riga, è necessario eseguire il commit esplicito.
Eroteev,

4
Il negozio del motore dovrebbe essere InnoDB, non 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";
}

?>

Per una domanda ampia e di alto profilo come questa, sarebbe bello se anche le risposte riflettessero questo. Il tuo esempio di codice è fantastico, ma puoi elaborarne altro? Spiegare le transazioni, perché, quando e dove? Infine, collega il codice con la tua spiegazione.
Dennis Haarbrink,

3
Benvenuto su StackOverflow. Scrivi sempre un testo descrittivo alla tua risposta.
Adrian Heine,

6
scusa im begginer, e il mio cattivo inglese, è un semplice esame del codice - per i principianti - commit () rollback () begin () messo in classe DB (ad esempio), $ query - non una volta - forse $ query0 $ query1 - quindi guardali - io uso questo codice, questo è molto facile da capire =)
Gedzberg Alex,

20
I suoi commenti rendono l'esempio abbastanza chiaro. Un buon codice non dovrebbe aver bisogno di descrivere il testo. Anche la domanda richiede un semplice esempio. Mi piace questa risposta.
Nessuna il

@GedzbergAlex per una singola query non c'è bisogno di transazione, semplicemente confonde sulla transazione, c'è un motivo per usare la transazione per una singola query?
24ıɥʇɹɐʞ ouɐɯ

35

Dato che questo è il primo risultato su Google per "transazione mysql php", ho pensato di aggiungere una risposta che dimostra esplicitamente come farlo con mysqli (come l'autore originale voleva esempi). Ecco un esempio semplificato di transazioni 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   
}

Inoltre, tieni presente che PHP 5.5 ha un nuovo metodo mysqli :: begin_transaction . Tuttavia, questo non è stato ancora documentato dal team di PHP, e sono ancora bloccato in PHP 5.3, quindi non posso commentarlo.


2
In una nota correlata, ho appena scoperto che se si lavora con le tabelle InnoDB, è possibile bloccare / sbloccare le tabelle quando si utilizza l'approccio autocomitt () alle transazioni, ma NON è possibile quando si utilizza l'approccio begin_transaction (): MySQL documentazione
EleventyOne,

+1 per un esempio dettagliato (e commentato) con il codice mysqli effettivo. Grazie per questo. E il tuo punto sul blocco / transazioni è davvero molto interessante.
a.real.human.being

1
"Autocommit (FALSE)" influirà su un'altra connessione nello stesso database / tabella? Voglio dire, se apriamo due pagine che una di esse imposta la sua connessione su "autocommit (FALSE)" ma l'altra lascia la funzione autocommit, attende la funzione commit o no. Voglio sapere se autocommit è un attributo per le connessioni e non per database / tabella. Grazie
Hamid

2
@Hamid $conn->autocommit(FALSE), nell'esempio sopra, sta interessando solo la singola connessione - non ha alcun effetto su altre connessioni al database.
EleventyOne,

1
NOTA: invece di if (!result), dovrebbe fare if (result === false), se la query è in grado di restituire un risultato valido che valuterà falso o zero.
ToolmakerSteve

10

Controlla quale motore di archiviazione stai utilizzando. Se è MyISAM, Transaction('COMMIT','ROLLBACK')non sarà supportato perché solo il motore di archiviazione InnoDB, non MyISAM, supporta le transazioni.


7

Quando si utilizza la connessione DOP:

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

Uso spesso il seguente codice per la gestione delle transazioni:

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

Esempio di utilizzo:

transaction(function()
{
    global $pdo;

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

In questo modo il codice di gestione delle transazioni non viene duplicato in tutto il progetto. Il che è positivo, perché, a giudicare da altre risposte basate sulla DOP in questo thread, è facile commettere errori in esso. I più comuni si dimenticano di riproporre l'eccezione e di avviare la transazione all'interno del tryblocco.


5

Ho creato una funzione per ottenere un vettore di query e fare una transazione, forse qualcuno lo scoprirà 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;
        }
    }

3

Ho avuto questo, ma non sono sicuro se questo è corretto. Potrebbe provare anche questo.

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 da qui: http://www.phpknowhow.com/mysql/transactions/


Codice non corretto trigger_error restituirà true (a meno che tu non abbia rovinato la tua chiamata), quindi $ result sarà sempre true, quindi questo codice salterà qualsiasi query non riuscita e tenterà sempre di eseguire il commit. Altrettanto preoccupante, stai usando il vecchio deprecato mysql_query, invece di usare mysqli, anche se ti colleghi a un tutorial che usa mysqli. IMHO, dovresti eliminare questo cattivo esempio o sostituirlo per usare il codice come scritto nel tutorial di phpknowhow.
ToolmakerSteve

2

Un altro esempio di stile procedurale con mysqli_multi_query, presuppone che $querysia pieno di istruzioni separate da punto e virgola.

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

1
Un po 'strano codice, ma almeno suggerisce l'uso di mysqli_multi_query. Chiunque sia interessato a questo dovrebbe cercare altrove su Google per un esempio più pulito.
ToolmakerSteve
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.