Transações com PDO
Esta aula ensina como usar transações com PDO em PHP para garantir a integridade dos dados em operações de banco de dados. Serão abordados os métodos beginTransaction, commit e rollback, quando utilizar transações, tratamento de erros e exemplos práticos.
Transações são fundamentais para garantir a consistência dos dados em operações que envolvem múltiplas consultas SQL. Com o PDO, é possível gerenciar transações de forma simples e eficiente, utilizando os métodos beginTransaction(), commit() e rollback(). Esta aula explora como implementar transações em PHP, quando utilizá-las e como tratar erros adequadamente.
Uma transação agrupa uma ou mais operações de banco de dados em uma única unidade lógica. Se todas as operações forem bem-sucedidas, a transação é confirmada (commit); caso contrário, todas as alterações são desfeitas (rollback). Isso evita que dados fiquem em estado inconsistente, como em transferências bancárias ou inserções em múltiplas tabelas.
beginTransaction, commit, rollback
O PDO oferece três métodos principais para gerenciar transações:
beginTransaction(): inicia uma transação. A partir deste ponto, todas as consultas são executadas em modo transacional até quecommit()ourollback()seja chamado.commit(): confirma todas as operações realizadas desde o início da transação, tornando-as permanentes no banco de dados.rollback(): desfaz todas as operações realizadas desde o início da transação, restaurando o estado anterior.
É importante verificar se o banco de dados suporta transações (ex.: MySQL com InnoDB, PostgreSQL). O PDO também oferece o método inTransaction() que retorna true se uma transação estiver ativa.
try {
$pdo = new PDO('mysql:host=localhost;dbname=teste', 'usuario', 'senha');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->beginTransaction();
// Operações SQL
$pdo->exec("INSERT INTO contas (nome, saldo) VALUES ('João', 1000)");
$pdo->exec("UPDATE contas SET saldo = saldo - 100 WHERE nome = 'Maria'");
$pdo->commit();
} catch (Exception $e) {
$pdo->rollback();
echo "Erro na transação: " . $e->getMessage();
}
Quando usar
Transações são necessárias sempre que a integridade dos dados depende de múltiplas operações serem executadas atomicamente. Exemplos comuns incluem:
- Transferências bancárias: debitar de uma conta e creditar em outra.
- Cadastro de pedidos: inserir pedido e itens do pedido.
- Operações que envolvem verificação de saldo ou estoque.
- Inserções em tabelas relacionadas que exigem consistência.
Evite usar transações para operações simples e independentes, pois elas adicionam overhead. Além disso, transações longas podem causar bloqueios no banco de dados, então mantenha-as curtas e rápidas.
Tratamento de erros
O tratamento de erros em transações deve garantir que qualquer falha resulte em rollback. A melhor prática é usar um bloco try-catch e chamar rollback() dentro do catch. É crucial configurar o modo de erro do PDO para exceções (PDO::ERRMODE_EXCEPTION) para que os erros sejam capturados adequadamente.
Além disso, é possível verificar o sucesso de cada operação individualmente, mas o uso de exceções simplifica o código. Cuidado com transações aninhadas: o PDO não suporta nativamente, mas você pode simular com savepoints.
try {
$pdo->beginTransaction();
// operações
$pdo->commit();
} catch (PDOException $e) {
$pdo->rollback();
// log do erro
error_log($e->getMessage());
echo "Transação falhou. Tente novamente.";
}
Exemplos
Exemplo 1: Transferência bancária
try {
$pdo = new PDO('mysql:host=localhost;dbname=banco', 'root', '');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->beginTransaction();
$pdo->exec("UPDATE contas SET saldo = saldo - 500 WHERE id = 1");
$pdo->exec("UPDATE contas SET saldo = saldo + 500 WHERE id = 2");
$pdo->commit();
echo "Transferência realizada com sucesso.";
} catch (Exception $e) {
$pdo->rollback();
echo "Erro na transferência: " . $e->getMessage();
}
Exemplo 2: Inserção de pedido e itens
try {
$pdo->beginTransaction();
$stmt = $pdo->prepare("INSERT INTO pedidos (cliente_id, data) VALUES (?, NOW())");
$stmt->execute([1]);
$pedidoId = $pdo->lastInsertId();
$stmt = $pdo->prepare("INSERT INTO itens_pedido (pedido_id, produto, quantidade) VALUES (?, ?, ?)");
$stmt->execute([$pedidoId, 'Teclado', 1]);
$stmt->execute([$pedidoId, 'Mouse', 2]);
$pdo->commit();
echo "Pedido criado com sucesso. ID: $pedidoId";
} catch (Exception $e) {
$pdo->rollback();
echo "Erro ao criar pedido: " . $e->getMessage();
}
Boas Práticas
- Sempre configure o modo de erro para exceções (
PDO::ERRMODE_EXCEPTION) ao usar transações. - Evite transações muito longas; mantenha apenas o necessário dentro do bloco transacional.
- Verifique se o banco de dados suporta transações e use o motor adequado (ex.: InnoDB no MySQL).
- Em sistemas críticos, considere o uso de savepoints para controle mais granular.
- Após o commit ou rollback, a transação é encerrada automaticamente; não é necessário chamar
beginTransaction()novamente para uma nova transação.
Referências
- PHP Manual: PDO::beginTransaction
- PHP Manual: PDO::commit
- PHP Manual: PDO::rollback
- PHP Manual: PDO::inTransaction
- MySQL Documentation: COMMIT and ROLLBACK
- PostgreSQL Documentation: Transactions
Exercícios
- Escreva um script PHP que realiza uma transferência bancária entre duas contas, utilizando transações. Inclua tratamento de erros com rollback.
- Crie um código que insira um novo usuário e seu perfil em duas tabelas diferentes (usuarios e perfis), garantindo que ambas as inserções sejam atômicas.
- Explique por que é importante configurar PDO::ATTR_ERRMODE para PDO::ERRMODE_EXCEPTION ao trabalhar com transações.
- Escreva um exemplo que utiliza savepoints para permitir rollback parcial dentro de uma transação.
- Crie um código que verifica se uma transação está ativa antes de tentar fazer commit ou rollback.
try {
$pdo = new PDO('mysql:host=localhost;dbname=banco', 'root', '');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->beginTransaction();
$pdo->exec("UPDATE contas SET saldo = saldo - 200 WHERE id = 1");
$pdo->exec("UPDATE contas SET saldo = saldo + 200 WHERE id = 2");
$pdo->commit();
echo "Transferência realizada.";
} catch (Exception $e) {
$pdo->rollback();
echo "Erro: " . $e->getMessage();
}
try {
$pdo->beginTransaction();
$stmt = $pdo->prepare("INSERT INTO usuarios (nome, email) VALUES (?, ?)");
$stmt->execute(['Maria', 'maria@email.com']);
$userId = $pdo->lastInsertId();
$stmt = $pdo->prepare("INSERT INTO perfis (usuario_id, bio) VALUES (?, ?)");
$stmt->execute([$userId, 'Bio da Maria']);
$pdo->commit();
echo "Usuário e perfil criados.";
} catch (Exception $e) {
$pdo->rollback();
echo "Erro: " . $e->getMessage();
}
try {
$pdo->beginTransaction();
$pdo->exec("INSERT INTO logs (mensagem) VALUES ('Início')");
$pdo->exec("SAVEPOINT sp1");
$pdo->exec("INSERT INTO logs (mensagem) VALUES ('Ponto 1')");
// Se algo der errado, pode reverter para sp1
$pdo->exec("ROLLBACK TO SAVEPOINT sp1");
$pdo->exec("INSERT INTO logs (mensagem) VALUES ('Após rollback')");
$pdo->commit();
} catch (Exception $e) {
$pdo->rollback();
}
if ($pdo->inTransaction()) {
$pdo->commit();
} else {
echo "Nenhuma transação ativa.";
}