Exemplos de transações PHP + MySQL

294

Eu realmente não encontrei um exemplo normal de arquivo PHP em que transações do MySQL estão sendo usadas. Você pode me mostrar um exemplo simples disso?

E mais uma pergunta. Eu já fiz muita programação e não usei transações. Posso colocar uma função PHP ou algo header.phpque, se um mysql_queryfalhar, os outros falham também?


Eu acho que descobri isso, certo ?:

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");
}
boa noite
fonte
10
Você pode usar mysql_query("BEGIN");em vez de seqüênciamysql_query("SET AUTOCOMMIT=0"); mysql_query("START TRANSACTION");
Kirzilla
75
Por favor, não use mysql_*funções no novo código . Eles não são mais mantidos e são oficialmente descontinuados . Veja a caixa vermelha ? Aprenda sobre as instruções preparadas e use o DOP ou o MySQLi - este artigo o ajudará a decidir quais. Se você escolher a DOP, aqui está um bom tutorial .
Naftali aka Neal
6
"Mysql_query (" SET AUTOCOMMIT = 0 "));" definir todas as conexões para aguardar a função de confirmação ou é apenas para a conexão relacionada?
Hamid
1
@Neal, na verdade mysqlwun die apesar de ter sido preterido, estará disponível no PECL para sempre.
Pacerier 17/10
2
@ Pacerier As coisas que são preteridas não "morrem". Eles são mantidos oficialmente para o software herdado, mas deixam de ser mantidos e afetados por quaisquer práticas recomendadas para o novo software. O fato permanece, não usemysql
taylorcressy

Respostas:

325

A ideia que geralmente uso ao trabalhar com transações se parece com isso (semi-pseudo-có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();
}


Observe que, com essa ideia, se uma consulta falhar, uma exceção deve ser lançada:

  • A DOP pode fazer isso, dependendo de como você a configura
  • caso contrário, com alguma outra API, talvez seja necessário testar o resultado da função usada para executar uma consulta e lançar você mesmo uma exceção.


Infelizmente, não há mágica envolvida. Você não pode simplesmente colocar uma instrução em algum lugar e realizar transações automaticamente: você ainda precisa especificar qual grupo de consultas deve ser executado em uma transação.

Por exemplo, muitas vezes você terá algumas consultas antes da transação (antes da begin) e outras após a transação (após commitou rollback) ou deseja que essas consultas sejam executadas, independentemente do que aconteceu (ou não) em a transação.

Pascal MARTIN
fonte
35
Tenha cuidado se estiver executando operações que possam gerar exceções que não sejam db. Nesse caso, uma exceção de uma instrução não db pode causar uma reversão inadvertida (mesmo se todas as chamadas db forem bem-sucedidas). Normalmente, você acha que reverter é uma boa idéia, mesmo que o erro não esteja no lado do banco de dados, mas há momentos em que códigos de terceiros / não críticos podem causar exceções não tão importantes e você ainda deseja continuar com a transação.
Halil Özgür
6
Qual é o $dbtipo aqui? mysqli?
Jake
3
@ Jake Veja minha resposta para um exemplo que usa mysqli (semelhante em estilo à abordagem de Pascal).
precisa
2
ele pode ser facilmente modificado para capturar PDOExceptione até verificar valores de exceção, se necessário. us2.php.net/PDOException
Yamiko
1
$ db é o objeto PDO (conexão). Ref: php.net/manual/en/pdo.connections.php
Fil
110

Eu acho que descobri isso, certo ?:

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");
}
boa noite
fonte
26
não é necessário definir a confirmação automática = 0. as transações sempre funcionam dessa maneira.
bgcode
2
@babonk - não tenho certeza se esse é o caso do InnoDB?
buggedcom
6
Eu acho que quando você inicia uma transação, ela funciona como se AUTOCOMMIT = 0
bgcode
4
@babonk está certo. Depois que uma transação é iniciada, AUTOCOMMIT = 0 está sendo definido implicitamente e depois que a transação é finalizada por confirmação ou reversão, o MySql retrocede o valor de AUTOCOMMIT que foi usado antes de iniciar a transação. OBSERVAÇÃO: você NÃO deve definir AUTOCOMMIT = 0, pois após confirmar as alterações, se você decidir inserir / atualizar outra linha, deverá confirmá-las explicitamente.
eroteev
4
A loja de mecanismos deve ser o InnoDB, não o 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
fonte
Para uma pergunta ampla e de alto perfil como essa, seria ótimo se as respostas também refletissem isso. Seu exemplo de código é ótimo, mas você pode elaborar mais? Explique sobre transações, por que, quando e onde? Por fim, vincule o código à sua explicação.
Dennis Haarbrink 18/10/12
3
Bem-vindo ao StackOverflow. Por favor, sempre escreva algum texto descritivo para sua resposta.
Adrian Heine
6
desculpe im iniciante, e meu inglês ruim, é muito fácil examinar o código - para iniciantes - commit () rollback () begin () colocado na classe DB (por exemplo), $ query - nem uma vez - talvez $ query0 $ query1 - e depois chek-los - eu uso este código, este muito fácil de entender =)
Gedzberg Alex
20
Seus comentários tornam o exemplo bem claro. Um bom código não precisa descrever o texto. Além disso, a pergunta pede um exemplo simples. Eu gosto desta resposta.
Nenhum
@GedzbergAlex para uma única consulta, não há necessidade de transação, simplesmente confunde a transação. Existe um motivo para usar a transação para uma única consulta?
ʞɔıɥʇɹɐʞ ouɐɯ
35

Como este é o primeiro resultado no google para "transação php mysql", pensei em adicionar uma resposta que demonstre explicitamente como fazer isso com o mysqli (como o autor original queria exemplos). Aqui está um exemplo simplificado de transações com 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   
}

Além disso, lembre-se de que o PHP 5.5 possui um novo método mysqli :: begin_transaction . No entanto, isso ainda não foi documentado pela equipe do PHP, e ainda estou preso no PHP 5.3, por isso não posso comentar.

EleventyOne
fonte
2
Em uma nota relacionada, eu acabei de descobrir que, se você estiver trabalhando com tabelas do InnoDB, é possível bloquear / desbloquear tabelas ao usar a abordagem autocomitt () para transações, mas NÃO é possível ao usar a abordagem begin_transaction (): MySQL documentação
EleventyOne
+1 para um exemplo detalhado (e comentado) com o código mysqli real. Obrigado por isso. E seu ponto sobre bloqueio / transações é realmente muito interessante.
precisa saber é o seguinte
1
A "confirmação automática (FALSE)" afetará outra conexão no mesmo banco de dados / tabela? Quero dizer, se abrirmos duas páginas em que uma delas definiu sua conexão como "confirmação automática (FALSE)", mas outra deixou a função de confirmação automática, ela espera pela função de confirmação ou não. Quero saber se a confirmação automática é um atributo para conexões e não para banco de dados / tabela. Obrigado
Hamid
2
@Hamid $conn->autocommit(FALSE), no exemplo acima, está afetando apenas a conexão individual - não tem efeito em nenhuma outra conexão com o banco de dados.
EleventyOne
1
NOTA: em vez de if (!result), deve funcionar if (result === false), se a consulta for capaz de retornar um resultado válido que seria avaliado como falso ou zero.
Página
10

Verifique qual mecanismo de armazenamento você está usando. Se for MyISAM, Transaction('COMMIT','ROLLBACK')não será suportado porque apenas o mecanismo de armazenamento InnoDB, não MyISAM, suporta transações.

dinesh
fonte
7

Ao usar a conexão PDO:

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

Costumo usar o seguinte código para gerenciamento de transações:

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

Exemplo de uso:

transaction(function()
{
    global $pdo;

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

Dessa forma, o código de gerenciamento de transações não é duplicado no projeto. O que é uma coisa boa, porque, a julgar por outras respostas com DOP neste tópico, é fácil cometer erros. Os mais comuns são esquecer de repetir a exceção e iniciar a transação dentro do trybloco.

Danila Piatov
fonte
5

Eu criei uma função para obter um vetor de consultas e fazer uma transação, talvez alguém ache ú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
fonte
3

Eu tinha isso, mas não tenho certeza se isso está correto. Poderia tentar isso também.

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éia daqui: http://www.phpknowhow.com/mysql/transactions/

nodeffect
fonte
Código incorreto. trigger_error retornará true (a menos que você tenha estragado sua ligação), portanto $ result será sempre true, portanto esse código perderá qualquer consulta com falha e sempre tentará confirmar. Igualmente preocupante, você está usando o antigo descontinuado mysql_query, em vez de usá-lo mysqli, mesmo que esteja vinculado a um tutorial usado mysqli. IMHO, você deve excluir este exemplo ruim ou substituí-lo para usar o código conforme escrito no tutorial do phpknowhow.
Home
2

Mais um exemplo de estilo de procedimento com mysqli_multi_query, assume que $queryé preenchido com instruções separadas por ponto e vírgula.

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

fonte
1
Código um tanto estranho, mas pelo menos sugere o uso de mysqli_multi_query. Qualquer pessoa interessada nisso deve procurar no Google em outro lugar para obter um exemplo mais limpo.
Página