Przykłady transakcji PHP + MySQL

294

Naprawdę nie znalazłem normalnego przykładu pliku PHP, w którym używane są transakcje MySQL. Czy możesz mi pokazać prosty przykład?

I jeszcze jedno pytanie. Już dużo programowałem i nie korzystałem z transakcji. Czy mogę wstawić funkcję PHP lub coś w header.phptym stylu, jeśli jedna mysql_queryzawiedzie, to pozostałe też zawiodą?


Myślę, że to rozgryzłem, prawda ?:

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");
}
dobry wieczór
źródło
10
mysql_query("BEGIN");Zamiast sekwencji można użyćmysql_query("SET AUTOCOMMIT=0"); mysql_query("START TRANSACTION");
Kirzilla,
75
Proszę nie używać mysql_*funkcji w nowym kodzie . Nie są już utrzymywane i oficjalnie przestarzałe . Widzisz czerwone pole ? Dowiedz sięzamiast tegoo przygotowanych instrukcjach i użyj PDO lub MySQLi - ten artykuł pomoże ci zdecydować, które. Jeśli wybierzesz PDO, oto dobry tutorial .
Naftali alias Neal
6
Czy „mysql_query („ SET AUTOCOMMIT = 0 ”);” ustawić wszystkie połączenia, aby czekały na funkcję zatwierdzenia, czy to tylko na powiązane z nią połączenie?
Hamid
1
@Neal, faktycznie nie działa, mysqlmimo że jest przestarzały, będzie dostępny w PECL na zawsze.
Pacerier
2
@Pacerier Rzeczy, które stają się przestarzałe, nie „giną”. Są one oficjalnie organizowane dla starszych wersji oprogramowania, ale przestają być utrzymywane i nie podlegają żadnym zalecanym praktykom dotyczącym nowego oprogramowania. Faktem jest, nie używajmysql
taylorcressy

Odpowiedzi:

325

Pomysł, którego zwykle używam podczas pracy z transakcjami, wygląda następująco (pół-pseudo-kod) :

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


Zauważ, że przy tym pomyśle, jeśli zapytanie nie powiedzie się, należy zgłosić wyjątek:

  • PDO może to zrobić, w zależności od konfiguracji
  • w przypadku niektórych innych interfejsów API może być konieczne przetestowanie wyniku funkcji użytej do wykonania zapytania i samodzielne zgłoszenie wyjątku.


Niestety nie ma w tym żadnej magii. Nie możesz po prostu gdzieś umieścić instrukcji, a transakcje są wykonywane automatycznie: nadal musisz określić, która grupa zapytań musi zostać wykonana w transakcji.

Na przykład dość często będziesz mieć kilka zapytań przed transakcją (przed begin) i kolejne kilka zapytań po transakcji (po jednym commitlub drugim rollback) i będziesz chciał, aby te zapytania były wykonywane bez względu na to, co się stało (lub nie) w transakcja.

Pascal MARTIN
źródło
35
Zachowaj ostrożność, jeśli wykonujesz operacje, które mogą generować wyjątki inne niż db. Jeśli tak, wyjątek od instrukcji innej niż db może przypadkowo spowodować wycofanie (nawet jeśli wszystkie wywołania db zakończą się powodzeniem). Zwykle można pomyśleć, że wycofanie jest dobrym pomysłem, nawet jeśli błąd nie był po stronie db, ale zdarzają się przypadki, że kod innej firmy / niekrytyczny kod może powodować nie tak ważne wyjątki, i nadal chcesz kontynuować transakcja.
Halil Özgür
6
Jaki jest $dbtutaj typ? mysqli?
Jake
3
@Jake Zobacz moją odpowiedź na przykład, który wykorzystuje mysqli (podobny styl do podejścia Pascala).
EleventyOne
2
można go łatwo modyfikować, aby wychwytywać, PDOExceptiona nawet sprawdzać wartości wyjątków, jeśli to konieczne. us2.php.net/PDOException
Yamiko
1
$ db to obiekt PDO (połączenie). Ref: php.net/manual/en/pdo.connections.php
Fil
110

Myślę, że to rozgryzłem, prawda ?:

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");
}
dobry wieczór
źródło
26
nie trzeba ustawiać autocommit = 0. transakcje zawsze działają w ten sposób.
bgcode
2
@babonk - nie jesteś pewien, czy tak jest w przypadku InnoDB?
buggedcom
6
Myślę, że po rozpoczęciu transakcji działa to tak, jakby AUTOCOMMIT = 0
bgcode
4
@babonk ma rację. Po rozpoczęciu transakcji domyślnie ustawiane jest AUTOCOMMIT = 0, a po zakończeniu transakcji przez zatwierdzenie lub wycofanie MySql przywraca wartość AUTOCOMMIT, która była używana przed rozpoczęciem transakcji. UWAGA: NIE powinieneś ustawiać AUTOCOMMIT = 0, ponieważ po zatwierdzeniu zmian, jeśli zdecydujesz się wstawić / zaktualizować kolejny wiersz, powinieneś go wyraźnie zatwierdzić.
eroteev
4
Sklep z silnikami powinien być InnoDB, a nie 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
źródło
W przypadku tak szerokiego i głośnego pytania, takie byłoby dobrze, gdyby odpowiedzi również to odzwierciedlały. Twój przykładowy kod jest świetny, ale czy możesz opracować więcej? Wyjaśnij o transakcjach, dlaczego, kiedy i gdzie? Na koniec połącz kod ze swoim wyjaśnieniem.
Dennis Haarbrink
3
Witamy w StackOverflow. Proszę zawsze pisać tekst opisujący odpowiedź.
Adrian Heine
6
przepraszam im Begginer i mój zły angielski, jego bardzo łatwy przykład kodu - dla początkujących - commit () rollback () begin () umieść w klasie DB (na przykład), $ query - nie raz - może $ query0 $ query1 - wtedy sprawdź je - używam tego kodu, to bardzo łatwe do zrozumienia =)
Gedzberg Alex
20
Jego komentarze wyjaśniają przykład. Dobry kod nie powinien wymagać opisywania tekstu. Również pytanie wymaga prostego przykładu. Podoba mi się ta odpowiedź.
Brak,
@GedzbergAlex dla pojedynczego zapytania nie ma potrzeby transakcji, po prostu myli się co do transakcji. Czy istnieje powód, aby użyć transakcji dla pojedynczego zapytania?
ɥʇɹɐʞıɥʇɹɐʞ ouɐɯ
35

Ponieważ jest to pierwszy wynik w Google dotyczący „transakcji php mysql”, pomyślałem, że dodam odpowiedź, która wyraźnie pokazuje, jak to zrobić za pomocą mysqli (tak jak chciał tego oryginalny autor). Oto uproszczony przykład transakcji z 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   
}

Pamiętaj również, że PHP 5.5 ma nową metodę mysqli :: begin_transaction . Jednak nie zostało to jeszcze udokumentowane przez zespół PHP i nadal tkwię w PHP 5.3, więc nie mogę tego komentować.

EleventyOne
źródło
2
W pokrewnej notatce właśnie odkryłem, że jeśli pracujesz z tabelami InnoDB, możliwe jest blokowanie / odblokowywanie tabel podczas korzystania z podejścia autocomitt () do transakcji, ale NIE jest to możliwe przy użyciu podejścia begin_transaction (): MySQL dokumentacja
EleventyOne
+1 za szczegółowy (i skomentowany) przykład z rzeczywistym kodem mysqli. Dzięki za to. A twoja uwaga na temat blokowania / transakcji jest naprawdę bardzo interesująca.
a.real.human.being
1
Czy „autocommit (FALSE)” wpłynie na inne połączenie w tej samej bazie danych / tabeli? Mam na myśli to, że jeśli otworzymy dwie strony, jedna z nich ustawi swoje połączenie na „autocommit (FALSE)”, ale druga opuści funkcję autocommit, czy czeka na funkcję zatwierdzenia, czy nie. Chcę wiedzieć, czy autocommit jest atrybutem dla połączeń, a nie dla bazy danych / tabeli. Dzięki
Hamid
2
@Hamid $conn->autocommit(FALSE)w powyższym przykładzie wpływa tylko na indywidualne połączenie - nie ma wpływu na żadne inne połączenia z bazą danych.
EleventyOne
1
UWAGA: zamiast if (!result), powinien zrobić if (result === false), jeśli zapytanie jest w stanie zwrócić poprawny wynik, którego wynikiem byłoby false lub zero.
ToolmakerSteve
10

Sprawdź, którego silnika pamięci używasz. Jeśli jest to MyISAM, Transaction('COMMIT','ROLLBACK')nie będzie obsługiwany, ponieważ tylko silnik pamięci InnoDB, a nie MyISAM, obsługuje transakcje.

dinesh
źródło
7

Podczas korzystania z połączenia PDO:

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

Często używam następującego kodu do zarządzania transakcjami:

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

Przykład użycia:

transaction(function()
{
    global $pdo;

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

W ten sposób kod zarządzania transakcjami nie jest duplikowany w całym projekcie. Co jest dobre, ponieważ sądząc po innych odpowiedziach powiązanych z PDO w tym wątku, łatwo jest popełnić w nim błędy. Najczęstsze to zapominanie o ponownym zwrocie wyjątku i rozpoczęcie transakcji w trybloku.

Danila Piatov
źródło
5

Zrobiłem funkcję, aby uzyskać wektor zapytań i wykonać transakcję, może ktoś uzna to za przydatne:

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
źródło
3

Miałem to, ale nie jestem pewien, czy to prawda. Mógłbym również to wypróbować.

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

Pomysł stąd: http://www.phpknowhow.com/mysql/transactions/

nieudolny
źródło
Niepoprawny kod. trigger_error zwróci true (chyba że spieprzyłeś swoje wywołanie), więc $ wynik zawsze będzie prawdziwy, więc ten kod będzie pomijał każde nieudane zapytanie i zawsze będzie próbował zatwierdzić. Równie niepokojące jest to, że używasz starej przestarzałej mysql_query, zamiast używać mysqli, nawet jeśli link do samouczka, który używa mysqli. IMHO, powinieneś albo usunąć ten zły przykład, albo zastąpić go, aby użyć kodu zapisanego w samouczku phpknowhow.
ToolmakerSteve,
2

Jeszcze jeden przykład stylu proceduralnego z mysqli_multi_query, zakłada, że $queryjest wypełniony instrukcjami oddzielonymi średnikami.

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

źródło
1
Nieco dziwny kod, ale przynajmniej sugeruje użycie mysqli_multi_query. Każdy zainteresowany powinien google gdzie indziej w celu uzyskania czystszego przykładu.
ToolmakerSteve,