A closure e a fronteira da ligação
Numa DB::transaction, uma exceção que sai da closure provoca rollback e é novamente lançada; o sucesso conduz ao commit. Esta descrição pressupõe operações transacionais abrangidas pela ligação e não elimina contratos especiais do motor. Se orders tem uma transação e audit faz uma escrita independente noutra ligação, o rollback de orders não estabelece rollback de audit.
Confirma a ligação efetiva de queries e modelos. Credenciais iguais ou nomes semelhantes não transformam duas ligações numa única transação.
Proteger a leitura que decide
Duas leituras de stock=1 fora da transação podem originar duas decisões favoráveis, mesmo que cada escrita posterior esteja numa transação. Num motor com locks de linha transacionais, lê com lockForUpdate dentro da transação que verifica e altera a disponibilidade.
Todos os concorrentes devem respeitar o mesmo protocolo. Obter um lock tarde e continuar a usar o valor antigo não atualiza a decisão. A unicidade tem também uma fronteira de escrita: duas validações prévias podem passar; uma restrição UNIQUE adequada arbitra inserções concorrentes e exige tratamento do conflito.
Repetição após deadlock e efeitos externos
O argumento de tentativas da transação permite repetir a closure perante deadlocks. Se uma tentativa chamou um serviço HTTP antes de falhar, esse serviço pode já ter aceite a ação. O rollback da base de dados não desfaz essa aceitação e a repetição pode duplicá-la.
Separa a persistência local da entrega externa e define identidade de operação, confirmação e comportamento de repetição adequados. A análise de aceitação deve indicar que efeitos ficaram pendentes ou confirmados; aumentar tentativas sem esse contrato apenas repete a incerteza.
DDL e commits implícitos
Não estendas automaticamente o contrato de escritas transacionais a todas as instruções SQL. A documentação assinala CREATE TABLE em MySQL como exemplo que pode causar commit implícito quando executado por statement ou unprepared. Laravel pode ficar sem conhecimento do nível real da transação no motor.
Se a operação posterior falhar, não é legítimo concluir que tudo regressou ao estado inicial só por existir uma closure transaction. Identifica instruções e motor e confirma o estado efetivo antes de planear recuperação.
Definir colunas, chaves e ordem dos modificadores
Uma migração descreve mudanças de esquema no método up e a sua reversão no down. As chamadas devem traduzir o contrato pretendido: reviewer_id opcional com referência a users pede nullable antes de constrained e o destino users explícito. Um valor zero por omissão não equivale a null nem prova que existe utilizador com esse ID.
Índices únicos e foreign keys impõem restrições distintas; validação da aplicação ajuda a explicar entradas inválidas, mas não substitui esses controlos sobre escritas concorrentes ou outros clientes.
Reverter estrutura e recuperar dados
Eliminar uma coluna com valores e recriá-la vazia no down recupera uma forma de esquema, não o histórico removido. Define como preservar ou recuperar valores antes de aceitar uma mudança destrutiva. Considera também processos antigos ainda ativos: web e workers podem coexistir e continuar a ler a coluna.
Adia a remoção até cessar essa dependência. O rollback de migrações atua sobre lotes, que podem conter vários ficheiros; escolhe o âmbito pretendido e reconcilia o resultado funcional em vez de tomar a presença de down como prova de reversibilidade completa.
Execução isolada e prova de aplicação
migrate --isolated usa um lock atómico da cache para impedir execuções concorrentes do comando. Instâncias distribuídas têm de partilhar o serviço central de cache que suporta o lock; a mesma chave em caches locais independentes não as coordena.
Uma instância que encontra o lock ocupado pode não executar migrações e ainda terminar com código de sucesso. Consulta o estado das migrações e confirma o resultado do executor efetivo. O lock coordena a execução, mas não demonstra compatibilidade das versões da aplicação com o novo esquema.
<?php
// Decision excerpt; imports, schema and request authorization are omitted.
// Assumes a transactional engine with row locks and the same protocol for all writers.
$db = DB::connection('orders');
$db->transaction(function () use ($db, $itemId) {
$stock = $db->table('stock')->where('id', $itemId)
->lockForUpdate()->first();
if ($stock === null || $stock->available < 1) {
throw new RuntimeException('No stock available');
}
$db->table('stock')->where('id', $itemId)
->update(['available' => $stock->available - 1]);
});
// A complete reservation also needs identity, persistence and retry contracts.
// Separate migration fragment: reviewer refers explicitly to users.
$table->foreignId('reviewer_id')->nullable()->constrained('users');
A decisão usa o valor lido com lock dentro da mesma transação que o altera, na ligação orders. Não reutiliza stock observado antes da transação. O excerto pressupõe um motor com locks de linha transacionais e escritores que seguem o protocolo; não apresenta essa garantia para SQLite.
A reserva completa, a identidade de repetição, os imports e o esquema não estão implementados. O fragmento de migração separado aplica nullable antes da foreign key explícita para users. A atividade é de interpretação, sem execução.
Armadilhas comuns
Ler stock antes da transação; supor que validação prévia garante unicidade; repetir chamadas externas dentro de uma closure sem contrato de repetição; esperar rollback de DDL MySQL; confundir recriação de coluna com recuperação dos seus valores; usar caches locais separadas para coordenar instâncias.
Tópicos relacionados: Eloquent, relações e custo das consultas · Filas, repetições e agendamento · Testes, cache e diagnóstico de releases
A garantia depende do intervalo protegido, da ligação, do motor e dos efeitos envolvidos. O sucesso de uma migração deve ser reconciliado com o esquema efetivo e os consumidores ativos. Recuperação inclui valores e funcionamento, além da existência de um método down.
Referência: Database transaction closure · Laravel 13.x; PHP 8.3+