Transações com PDO
1. Introdução às Transações no PDO
Transações são unidades lógicas de trabalho que garantem que um conjunto de operações no banco de dados seja executado de forma atômica. Isso significa que todas as operações dentro da transação devem ser concluídas com sucesso ou, em caso de falha, nenhuma delas deve ter efeito permanente. Esse princípio segue o acrônimo ACID: Atomicidade, Consistência, Isolamento e Durabilidade.
No contexto do PDO (PHP Data Objects), transações são essenciais para operações que envolvem múltiplas instruções SQL que precisam ser tratadas como uma única unidade. Exemplos clássicos incluem transferências bancárias, processamento de pedidos, ou qualquer cenário onde a integridade dos dados depende da execução completa de um conjunto de operações.
Para utilizar transações com PDO, seu ambiente PHP precisa ter a extensão PDO habilitada e o banco de dados (MySQL, PostgreSQL, SQLite, etc.) deve suportar transações. Felizmente, todos os principais SGBDs oferecem esse suporte.
2. Iniciando e Finalizando Transações
O PDO fornece três métodos fundamentais para gerenciar transações:
PDO::beginTransaction()— desativa o modo autocommit e inicia uma transaçãoPDO::commit()— confirma todas as operações realizadas desde o início da transaçãoPDO::rollBack()— reverte todas as operações realizadas desde o início da transação
Vamos ver um exemplo prático de transferência bancária entre duas contas:
<?php
try {
$pdo = new PDO('mysql:host=localhost;dbname=banco', 'usuario', 'senha');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Inicia a transação
$pdo->beginTransaction();
// Deduz 500 da conta de origem
$stmt = $pdo->prepare("UPDATE contas SET saldo = saldo - ? WHERE id = ?");
$stmt->execute([500, 1]);
// Adiciona 500 à conta de destino
$stmt = $pdo->prepare("UPDATE contas SET saldo = saldo + ? WHERE id = ?");
$stmt->execute([500, 2]);
// Confirma as alterações
$pdo->commit();
echo "Transferência realizada com sucesso!";
} catch (PDOException $e) {
// Em caso de erro, reverte tudo
$pdo->rollBack();
echo "Erro na transferência: " . $e->getMessage();
}
3. Tratamento de Erros e Exceções em Transações
O uso de try-catch é a abordagem recomendada para gerenciar transações, pois garante que qualquer exceção seja capturada e a transação seja revertida adequadamente.
<?php
function transferir($pdo, $origem, $destino, $valor) {
try {
$pdo->beginTransaction();
// Verifica saldo da conta de origem
$stmt = $pdo->prepare("SELECT saldo FROM contas WHERE id = ? FOR UPDATE");
$stmt->execute([$origem]);
$saldoOrigem = $stmt->fetchColumn();
if ($saldoOrigem < $valor) {
throw new Exception("Saldo insuficiente na conta de origem");
}
$pdo->prepare("UPDATE contas SET saldo = saldo - ? WHERE id = ?")
->execute([$valor, $origem]);
$pdo->prepare("UPDATE contas SET saldo = saldo + ? WHERE id = ?")
->execute([$valor, $destino]);
$pdo->commit();
return true;
} catch (Exception $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
throw $e; // Relança a exceção para tratamento externo
}
}
Note o uso de PDO::inTransaction() para verificar se a transação ainda está ativa antes de chamar rollBack(). Isso evita erros caso a transação já tenha sido finalizada por algum motivo.
4. Transações Aninhadas e Comportamento do SGBD
O PDO não suporta transações aninhadas nativamente. Chamar beginTransaction() enquanto uma transação já está ativa lançará uma exceção. No entanto, é possível simular savepoints usando comandos SQL diretamente:
<?php
$pdo->beginTransaction();
// Cria um savepoint
$pdo->exec("SAVEPOINT ponto1");
// Operações parciais
$pdo->exec("UPDATE produtos SET estoque = estoque - 1 WHERE id = 10");
// Se algo der errado, pode reverter até o savepoint
$pdo->exec("ROLLBACK TO SAVEPOINT ponto1");
// Ou confirmar tudo
$pdo->commit();
É importante conhecer as diferenças entre SGBDs:
- MySQL (InnoDB): suporta transações e savepoints
- PostgreSQL: suporte completo a transações e savepoints
- SQLite: suporta transações, mas com limitações em concorrência
- MyISAM (MySQL): não suporta transações
5. Isolamento de Transações e Bloqueios
Os níveis de isolamento definem como as transações interagem entre si. O PDO permite configurar o nível via PDO::exec():
<?php
// Configura nível de isolamento para evitar leitura suja
$pdo->exec("SET TRANSACTION ISOLATION LEVEL READ COMMITTED");
$pdo->beginTransaction();
// Consulta que não verá dados não confirmados de outras transações
$stmt = $pdo->query("SELECT saldo FROM contas WHERE id = 1");
Os níveis disponíveis são:
- READ UNCOMMITTED: menor isolamento, permite leitura suja
- READ COMMITTED: evita leitura suja (padrão no PostgreSQL)
- REPEATABLE READ: garante leituras consistentes (padrão no MySQL InnoDB)
- SERIALIZABLE: maior isolamento, executa transações como se fossem sequenciais
6. Práticas Avançadas com Transações
Combinação de transações com prepared statements e operações em lote:
<?php
$pdo->beginTransaction();
try {
$stmt = $pdo->prepare("INSERT INTO logs (usuario_id, acao, data) VALUES (?, ?, NOW())");
// Inserção em massa dentro da transação
$dados = [
[1, 'login'],
[2, 'logout'],
[3, 'atualizacao'],
];
foreach ($dados as $linha) {
$stmt->execute($linha);
}
// lastInsertId() funciona dentro da transação
$ultimoId = $pdo->lastInsertId();
$pdo->commit();
} catch (Exception $e) {
$pdo->rollBack();
echo "Erro no processamento em lote: " . $e->getMessage();
}
7. Boas Práticas e Erros Comuns
Boas práticas:
- Sempre verifique se a transação está ativa antes de commit() ou rollBack()
- Mantenha transações curtas para evitar bloqueios prolongados
- Use try-catch-finally para garantir o fechamento da transação
- Configure PDO::ATTR_ERRMODE para PDO::ERRMODE_EXCEPTION
Erros comuns:
// ERRADO: esquecer de tratar exceções
$pdo->beginTransaction();
$pdo->query("UPDATE contas SET saldo = saldo - 100 WHERE id = 1");
// Se ocorrer erro aqui, a transação fica aberta!
$pdo->commit();
// CORRETO
try {
$pdo->beginTransaction();
$pdo->query("UPDATE contas SET saldo = saldo - 100 WHERE id = 1");
$pdo->commit();
} catch (Exception $e) {
$pdo->rollBack();
}
8. Conclusão e Comparação com Outras Abordagens
Transações com PDO oferecem controle fino e direto sobre operações de banco de dados em PHP. Embora ORMs como Doctrine (com EntityManager::flush()) abstraiam esse gerenciamento, o PDO puro é mais leve e adequado para aplicações que exigem performance e controle granular.
Quando usar PDO puro:
- Aplicações simples ou legadas
- Operações que exigem controle preciso de transações
- Performance crítica
Quando optar por abstrações:
- Projetos complexos com múltiplas entidades
- Equipes que preferem abstração de banco de dados
- Necessidade de portabilidade entre SGBDs
Dominar transações com PDO é fundamental para qualquer desenvolvedor PHP que trabalhe com bancos de dados relacionais, garantindo integridade e confiabilidade nas operações.
Referências
- PHP: PDO::beginTransaction - Manual oficial — Documentação oficial do PHP sobre o método beginTransaction do PDO
- PHP: PDO::commit - Manual oficial — Documentação oficial sobre o método commit do PDO
- PHP: PDO::rollBack - Manual oficial — Documentação oficial sobre o método rollBack do PDO
- MySQL: InnoDB Transaction Model — Modelo de transações do MySQL com InnoDB, incluindo níveis de isolamento
- PostgreSQL: Transaction Isolation — Documentação oficial do PostgreSQL sobre controle de concorrência e isolamento de transações
- SQLite: Transaction Behavior — Documentação oficial do SQLite sobre comportamento de transações e savepoints
- PHP The Right Way: Transactions — Guia prático sobre boas práticas com transações no PDO